Merge "Fix EGL negative api eglMakeCurrent test."
diff --git a/framework/delibs/decpp/deStringUtil.cpp b/framework/delibs/decpp/deStringUtil.cpp
index c5e0166..14fccaa 100644
--- a/framework/delibs/decpp/deStringUtil.cpp
+++ b/framework/delibs/decpp/deStringUtil.cpp
@@ -22,6 +22,7 @@
  *//*--------------------------------------------------------------------*/
 
 #include "deStringUtil.hpp"
+#include "deString.h"
 
 #include <algorithm>
 #include <iterator>
@@ -113,6 +114,22 @@
 	return s.str();
 }
 
+bool beginsWith (const std::string& s, const std::string& prefix)
+{
+	return deStringBeginsWith(s.c_str(), prefix.c_str());
+}
+
+bool endsWith (const std::string& s, const std::string& suffix)
+{
+	if (suffix.length() > s.length())
+		return false;
+	else
+	{
+		const std::string::size_type offset = s.length() - suffix.length();
+		return s.find(suffix, offset) == offset;
+	}
+}
+
 char toUpper (char c)
 {
 	return std::toupper(c, std::locale::classic());
@@ -156,6 +173,25 @@
 
 	DE_TEST_ASSERT(floatToString(4, 1) == "4.0");
 
+	DE_TEST_ASSERT(beginsWith("foobar", "foobar"));
+	DE_TEST_ASSERT(beginsWith("foobar", "foo"));
+	DE_TEST_ASSERT(beginsWith("foobar", "f"));
+	DE_TEST_ASSERT(beginsWith("foobar", ""));
+	DE_TEST_ASSERT(beginsWith("", ""));
+	DE_TEST_ASSERT(!beginsWith("foobar", "bar"));
+	DE_TEST_ASSERT(!beginsWith("foobar", "foobarbaz"));
+	DE_TEST_ASSERT(!beginsWith("", "foo"));
+
+	DE_TEST_ASSERT(endsWith("foobar", "foobar"));
+	DE_TEST_ASSERT(endsWith("foobar", "bar"));
+	DE_TEST_ASSERT(endsWith("foobar", "r"));
+	DE_TEST_ASSERT(endsWith("foobar", ""));
+	DE_TEST_ASSERT(endsWith("", ""));
+	DE_TEST_ASSERT(!endsWith("foobar", "foo"));
+	DE_TEST_ASSERT(!endsWith("foobar", "bazfoobar"));
+	DE_TEST_ASSERT(!endsWith("foobar", "foobarbaz"));
+	DE_TEST_ASSERT(!endsWith("", "foo"));
+
 	DE_TEST_ASSERT(toUpper('a') == 'A');
 	DE_TEST_ASSERT(toUpper('A') == 'A');
 	DE_TEST_ASSERT(toLower('a') == 'a');
diff --git a/framework/delibs/decpp/deStringUtil.hpp b/framework/delibs/decpp/deStringUtil.hpp
index 68614d8..343619c 100644
--- a/framework/delibs/decpp/deStringUtil.hpp
+++ b/framework/delibs/decpp/deStringUtil.hpp
@@ -47,6 +47,8 @@
 std::string					capitalize		(const std::string& s);
 std::vector<std::string>	splitString		(const std::string& s, char delim='\0');
 std::string					floatToString	(float val, int precision);
+bool						beginsWith		(const std::string& s, const std::string& prefix);
+bool						endsWith		(const std::string& s, const std::string& suffix);
 char						toUpper			(char c);
 char						toLower			(char c);
 bool						isUpper			(char c);
diff --git a/modules/gles2/functional/es2fFboCompletenessTests.cpp b/modules/gles2/functional/es2fFboCompletenessTests.cpp
index 5a67f8d..63be7b3 100644
--- a/modules/gles2/functional/es2fFboCompletenessTests.cpp
+++ b/modules/gles2/functional/es2fFboCompletenessTests.cpp
@@ -102,6 +102,38 @@
 	GL_SRGB8_ALPHA8
 };
 
+// DEQP_gles3_core_no_extension_features
+static const FormatKey s_es3NoExtRboFormats[] =
+{
+	GL_RGB10_A2,
+};
+static const FormatKey s_es3NoExtTextureFormats[] =
+{
+	GL_R16F,
+	GL_RG16F,
+	GL_RGB16F,
+	GL_RGBA16F,
+	GL_R11F_G11F_B10F,
+};
+static const FormatKey s_es3NoExtTextureColorRenderableFormats[] =
+{
+	GL_R8,
+	GL_RG8,
+};
+
+// with ES3 core and GL_EXT_color_buffer_float
+static const FormatKey s_es3NoExtExtColorBufferFloatFormats[] =
+{
+	// \note Only the GLES2+exts subset of formats
+	GL_R11F_G11F_B10F, GL_RGBA16F, GL_RG16F, GL_R16F,
+};
+
+// with ES3 core with OES_texture_stencil8
+static const FormatKey s_es3NoExtOesTextureStencil8Formats[] =
+{
+	GL_STENCIL_INDEX8,
+};
+
 static const FormatExtEntry s_es2ExtFormats[] =
 {
 	// The extension does not specify these to be color-renderable.
@@ -122,12 +154,47 @@
 		REQUIRED_RENDERABLE | TEXTURE_VALID | COLOR_RENDERABLE | RENDERBUFFER_VALID,
 		GLS_ARRAY_RANGE(s_extSrgbWriteControlFormats)
 	},
+
+	// Since GLES3 is "backwards compatible" to GLES2, we might actually be running on a GLES3
+	// context. Since GLES3 added some features to core with no corresponding GLES2 extension,
+	// some tests might produce wrong results (since they are using rules of GLES2 & extensions)
+	//
+	// To avoid this, require new features of GLES3 that have no matching GLES2 extension if
+	// context is GLES3. This can be done with a DEQP_* extensions.
+	//
+	// \note Not all feature changes are listed here but only those that alter GLES2 subset of
+	//       the formats
+	{
+		"DEQP_gles3_core_compatible",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_es3NoExtRboFormats)
+	},
+	{
+		"DEQP_gles3_core_compatible",
+		TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_es3NoExtTextureFormats)
+	},
+	{
+		"DEQP_gles3_core_compatible",
+		REQUIRED_RENDERABLE | TEXTURE_VALID | COLOR_RENDERABLE,
+		GLS_ARRAY_RANGE(s_es3NoExtTextureColorRenderableFormats)
+	},
+	{
+		"DEQP_gles3_core_compatible GL_EXT_color_buffer_float",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_es3NoExtExtColorBufferFloatFormats)
+	},
+	{
+		"DEQP_gles3_core_compatible GL_OES_texture_stencil8",
+		REQUIRED_RENDERABLE | STENCIL_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_es3NoExtOesTextureStencil8Formats)
+	},
 };
 
 class ES2Checker : public Checker
 {
 public:
-			ES2Checker				(void) : m_width(-1), m_height(-1) {}
+			ES2Checker				(const glu::RenderContext& ctx);
 	void	check					(GLenum attPoint, const Attachment& att,
 									 const Image* image);
 private:
@@ -135,7 +202,14 @@
 	GLsizei	m_height;	//< The common height of images
 };
 
-void ES2Checker::check(GLenum attPoint, const Attachment& att, const Image* image)
+ES2Checker::ES2Checker (const glu::RenderContext& ctx)\
+	: Checker		(ctx)
+	, m_width		(-1)
+	, m_height		(-1)
+{
+}
+
+void ES2Checker::check (GLenum attPoint, const Attachment& att, const Image* image)
 {
 	DE_UNREF(attPoint);
 	DE_UNREF(att);
@@ -145,10 +219,16 @@
 		m_width = image->width;
 		m_height = image->height;
 	}
-	else
+	else if (image->width != m_width || image->height != m_height)
 	{
-		require(image->width == m_width && image->height == m_height,
-				GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
+		// Since GLES3 is "backwards compatible" to GLES2, we might actually be running
+		// on a GLES3 context. On GLES3, FRAMEBUFFER_INCOMPLETE_DIMENSIONS is not generated
+		// if attachments have different sizes.
+		if (!gls::FboUtil::checkExtensionSupport(m_renderCtx, "DEQP_gles3_core_compatible"))
+		{
+			// running on GLES2
+			addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS, "Sizes of attachments differ");
+		}
 	}
 	// GLES2, 4.4.5: "some implementations may not support rendering to
 	// particular combinations of internal formats. If the combination of
@@ -157,7 +237,7 @@
 	// under the clause labeled FRAMEBUFFER_UNSUPPORTED."
 	//
 	// Hence it is _always_ allowed to report FRAMEBUFFER_UNSUPPORTED.
-	canRequire(false, GL_FRAMEBUFFER_UNSUPPORTED);
+	addPotentialFBOStatus(GL_FRAMEBUFFER_UNSUPPORTED, "Particular format combinations need not to be supported");
 }
 
 struct FormatCombination
@@ -204,7 +284,7 @@
 	if (fmt.format == GL_NONE)
 		return GL_NONE;
 
-	const FormatFlags flags = m_ctx.getMinFormats().getFormatInfo(fmt, ANY_FORMAT);
+	const FormatFlags flags = m_ctx.getCoreFormats().getFormatInfo(fmt);
 	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));
@@ -214,7 +294,7 @@
 
 IterateResult SupportedCombinationTest::iterate (void)
 {
-	const FormatDB& db		= m_ctx.getMinFormats();
+	const FormatDB& db		= m_ctx.getCoreFormats();
 	const ImageFormat none	= ImageFormat::none();
 	Formats colorFmts		= db.getFormats(COLOR_RENDERABLE);
 	Formats depthFmts		= db.getFormats(DEPTH_RENDERABLE);
@@ -254,9 +334,10 @@
 	return STOP;
 }
 
-class ES2CheckerFactory : public CheckerFactory {
+class ES2CheckerFactory : public CheckerFactory
+{
 public:
-	Checker*			createChecker	(void) { return new ES2Checker(); }
+	Checker*			createChecker	(const glu::RenderContext& ctx) { return new ES2Checker(ctx); }
 };
 
 class TestGroup : public TestCaseGroup
diff --git a/modules/gles3/functional/es3fFboCompletenessTests.cpp b/modules/gles3/functional/es3fFboCompletenessTests.cpp
index b392f51..0e06a18 100644
--- a/modules/gles3/functional/es3fFboCompletenessTests.cpp
+++ b/modules/gles3/functional/es3fFboCompletenessTests.cpp
@@ -136,26 +136,47 @@
 	GL_STENCIL_INDEX8,
 };
 
+// GL_EXT_render_snorm
+static const FormatKey s_extRenderSnorm[] =
+{
+	GL_R8_SNORM, GL_RG8_SNORM, GL_RGBA8_SNORM,
+};
 
 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) },
-	{ "GL_OES_texture_stencil8",
-	  // Note: es3 RBO tests actually cover the first two requirements
-      // - kept here for completeness
-      REQUIRED_RENDERABLE | STENCIL_RENDERABLE | TEXTURE_VALID,
-	  GLS_ARRAY_RANGE(s_extOESTextureStencil8) }
+	{
+		"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)
+	},
+	{
+		"GL_OES_texture_stencil8",
+		// \note: es3 RBO tests actually cover the first two requirements
+		// - kept here for completeness
+		REQUIRED_RENDERABLE | STENCIL_RENDERABLE | TEXTURE_VALID,
+		GLS_ARRAY_RANGE(s_extOESTextureStencil8)
+	},
+
+	// Since GLES31 is backwards compatible to GLES3, we might actually be running on a GLES31.
+	// Add rule changes of GLES31 that have no corresponding GLES3 extension.
+	//
+	// \note Not all feature changes are listed here but only those that alter GLES3 subset of
+	//       the formats
+	{
+		"DEQP_gles31_core_compatible GL_EXT_render_snorm",
+		REQUIRED_RENDERABLE | COLOR_RENDERABLE | TEXTURE_VALID | RENDERBUFFER_VALID,
+		GLS_ARRAY_RANGE(s_extRenderSnorm)
+	},
 };
 
 class ES3Checker : public Checker
 {
 public:
-				ES3Checker	(void)
-					: m_numSamples			(-1)
+				ES3Checker	(const glu::RenderContext& ctx)
+					: Checker				(ctx)
+					, m_numSamples			(-1)
 					, m_depthStencilImage	(0)
 					, m_depthStencilType	(GL_NONE) {}
 	void		check 		(GLenum attPoint, const Attachment& att, const Image* image);
@@ -191,16 +212,16 @@
 
 		// 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 ((m_numSamples == 0) != (imgSamples == 0))
+			addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, "Mixed multi- and single-sampled attachments");
 
 		// 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);
+		if (m_numSamples != imgSamples)
+			addPotentialFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, "Number of samples differ");
 	}
 
 	// "Depth and stencil attachments, if present, are the same image."
@@ -212,9 +233,10 @@
 			m_depthStencilType = attachmentType(att);
 		}
 		else
-			require(m_depthStencilImage == att.imageName &&
-					m_depthStencilType == attachmentType(att),
-					GL_FRAMEBUFFER_UNSUPPORTED);
+		{
+			if (m_depthStencilImage != att.imageName || m_depthStencilType != attachmentType(att))
+				addFBOStatus(GL_FRAMEBUFFER_UNSUPPORTED, "Depth and stencil attachments are not the same image");
+		}
 	}
 }
 
@@ -413,7 +435,7 @@
 class ES3CheckerFactory : public CheckerFactory
 {
 public:
-	Checker*			createChecker	(void) { return new ES3Checker(); }
+	Checker*			createChecker	(const glu::RenderContext& ctx) { return new ES3Checker(ctx); }
 };
 
 class TestGroup : public TestCaseGroup
diff --git a/modules/gles31/functional/es31fProgramInterfaceDefinition.cpp b/modules/gles31/functional/es31fProgramInterfaceDefinition.cpp
index 88ae724..6d8f737 100644
--- a/modules/gles31/functional/es31fProgramInterfaceDefinition.cpp
+++ b/modules/gles31/functional/es31fProgramInterfaceDefinition.cpp
@@ -26,6 +26,7 @@
 #include "gluVarType.hpp"
 #include "gluShaderProgram.hpp"
 #include "deSTLUtil.hpp"
+#include "deStringUtil.hpp"
 #include "glwEnums.hpp"
 
 #include <set>
@@ -634,7 +635,7 @@
 	for (int ndx = 0; ndx < (int)m_xfbVaryings.size(); ++ndx)
 	{
 		// user-defined
-		if (m_xfbVaryings[ndx].find("gl_") != 0)
+		if (!de::beginsWith(m_xfbVaryings[ndx], "gl_"))
 		{
 			std::vector<ProgramInterfaceDefinition::VariablePathComponent> path;
 			if (!findProgramVariablePathByPathName(path, this, m_xfbVaryings[ndx], VariableSearchFilter::createShaderTypeStorageFilter(getProgramTransformFeedbackStage(this), glu::STORAGE_OUT)))
diff --git a/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.cpp b/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.cpp
index 15c6431..778ef6f 100644
--- a/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.cpp
+++ b/modules/gles31/functional/es31fProgramInterfaceQueryTestCase.cpp
@@ -46,14 +46,6 @@
 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)
@@ -1966,7 +1958,7 @@
 
 			if (recoveryStrategies[strategyNdx].removeTrailingArray)
 			{
-				if (stringEndsWith(resourceName, "[0]"))
+				if (de::endsWith(resourceName, "[0]"))
 					simplifiedResourceName = resourceName.substr(0, resourceName.length() - 3);
 				else
 					continue;
diff --git a/modules/gles31/functional/es31fProgramInterfaceQueryTests.cpp b/modules/gles31/functional/es31fProgramInterfaceQueryTests.cpp
index 0bc1efc..bfc85d9 100644
--- a/modules/gles31/functional/es31fProgramInterfaceQueryTests.cpp
+++ b/modules/gles31/functional/es31fProgramInterfaceQueryTests.cpp
@@ -52,14 +52,6 @@
 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)
@@ -1106,7 +1098,7 @@
 	{
 		for (int ndx = 0; ndx < (int)referenceResources.size(); ++ndx)
 		{
-			if (stringEndsWith(referenceResources[ndx], "[0]"))
+			if (de::endsWith(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());
@@ -1233,7 +1225,7 @@
 				if (arrayedInterface && isImplicitlySizedArray)
 				{
 					// omit implicit arrayness from name, i.e. remove trailing "_array"
-					DE_ASSERT(stringEndsWith(buf, "_array"));
+					DE_ASSERT(de::endsWith(buf, "_array"));
 					buf = buf.substr(0, buf.length() - 6);
 				}
 
diff --git a/modules/gles31/functional/es31fTessellationTests.cpp b/modules/gles31/functional/es31fTessellationTests.cpp
index b17463f..f3f938a 100644
--- a/modules/gles31/functional/es31fTessellationTests.cpp
+++ b/modules/gles31/functional/es31fTessellationTests.cpp
@@ -5861,10 +5861,16 @@
 		const glu::VarType	highpFloat		(glu::TYPE_FLOAT, glu::PRECISION_HIGHP);
 		glu::StructType&	structType		= m_structTypes.back();
 		const glu::VarType	structVarType	(&structType);
+		bool				usedStruct		= false;
 
 		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)
+		{
+			// It is illegal to have a structure containing an array as an output variable
+			structType.addMember("z", glu::VarType(highpFloat, 2));
+		}
 
 		if (useBlock)
 		{
@@ -5876,20 +5882,32 @@
 
 			m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "tcBlock", blockMembers)));
 			m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "teBlock", blockMembers)));
+
+			usedStruct = true;
 		}
 		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)));
+			if (m_ioType != IO_TYPE_PER_PATCH_ARRAY)
+			{
+				// Arrays of structures are disallowed, add struct cases only if not arrayed variable
+				m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var0)));
+				m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var0)));
+
+				usedStruct = true;
+			}
+
 			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";
+
+		if (usedStruct)
+			tcsDeclarations += de::toString(glu::declare(structType)) + ";\n";
+
 		tcsStatements += "\t{\n"
 						 "\t\thighp float v = 1.3;\n";
 
diff --git a/modules/glshared/glsFboCompletenessTests.cpp b/modules/glshared/glsFboCompletenessTests.cpp
index fc93f49..cb84808 100644
--- a/modules/glshared/glsFboCompletenessTests.cpp
+++ b/modules/glshared/glsFboCompletenessTests.cpp
@@ -45,6 +45,7 @@
 using tcu::TestNode;
 using std::string;
 using de::toString;
+using de::toLower;
 using namespace deqp::gls::FboUtil;
 using namespace deqp::gls::FboUtil::config;
 typedef TestCase::IterateResult IterateResult;
@@ -58,14 +59,6 @@
 
 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.
 
@@ -406,7 +399,7 @@
 				  CheckerFactory& factory)
 	: m_testCtx				(testCtx)
 	, m_renderCtx			(renderCtx)
-	, m_verifier			(m_ctxFormats, factory)
+	, m_verifier			(m_ctxFormats, factory, renderCtx)
 	, m_haveMultiColorAtts	(false)
 {
 	FormatExtEntries extRange = GLS_ARRAY_RANGE(s_esExtFormats);
@@ -415,15 +408,15 @@
 
 void Context::addFormats (FormatEntries fmtRange)
 {
-	FboUtil::addFormats(m_minFormats, fmtRange);
+	FboUtil::addFormats(m_coreFormats, fmtRange);
 	FboUtil::addFormats(m_ctxFormats, fmtRange);
-	FboUtil::addFormats(m_maxFormats, fmtRange);
+	FboUtil::addFormats(m_allFormats, fmtRange);
 }
 
 void Context::addExtFormats (FormatExtEntries extRange)
 {
 	FboUtil::addExtFormats(m_ctxFormats, extRange, &m_renderCtx);
-	FboUtil::addExtFormats(m_maxFormats, extRange, DE_NULL);
+	FboUtil::addExtFormats(m_allFormats, extRange, DE_NULL);
 }
 
 void TestBase::pass (void)
@@ -441,76 +434,249 @@
 	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();
 }
 
+static bool isFormatFeatureSupported (const FormatDB& db, const ImageFormat& format, FormatFlags feature)
+{
+	return db.isKnownFormat(format) && ((db.getFormatInfo(format) & feature) == feature);
+}
+
+static void logAffectingExtensions (const char* prefix, const FormatDB& db, const ImageFormat& format, FormatFlags feature, tcu::MessageBuilder& msg)
+{
+	const std::set<std::set<std::string> > rows = db.getFormatFeatureExtensions(format, feature);
+
+	for (std::set<std::set<std::string> >::const_iterator rowIt = rows.begin(); rowIt != rows.end(); ++rowIt)
+	{
+		const std::set<std::string>&			requiredExtensions	= *rowIt;
+		std::set<std::string>::const_iterator	it					= requiredExtensions.begin();
+		std::string								extName;
+
+		msg << prefix;
+
+		extName = *it++;
+		while (it != requiredExtensions.end())
+		{
+			msg << getExtensionDescription(extName);
+			extName = *it++;
+			msg << (it == requiredExtensions.end() ? " and " : ", ");
+		}
+
+		msg << getExtensionDescription(extName) << '\n';
+	}
+}
+
+static void logFormatInfo (const config::Framebuffer& fbo, const FormatDB& ctxFormats, const FormatDB& coreFormats, const FormatDB& allFormats, tcu::TestLog& log)
+{
+	static const struct
+	{
+		const char*			name;
+		const FormatFlags	flag;
+	} s_renderability[] =
+	{
+		{ "color-renderable",	COLOR_RENDERABLE	},
+		{ "depth-renderable",	DEPTH_RENDERABLE	},
+		{ "stencil-renderable",	STENCIL_RENDERABLE	},
+	};
+
+	std::set<ImageFormat> formats;
+
+	for (config::TextureMap::const_iterator it = fbo.textures.begin(); it != fbo.textures.end(); ++it)
+		formats.insert(it->second->internalFormat);
+	for (config::RboMap::const_iterator it = fbo.rbos.begin(); it != fbo.rbos.end(); ++it)
+		formats.insert(it->second->internalFormat);
+
+	if (!formats.empty())
+	{
+		const tcu::ScopedLogSection supersection(log, "Format", "Format info");
+
+		for (std::set<ImageFormat>::const_iterator it = formats.begin(); it != formats.end(); ++it)
+		{
+			const tcu::ScopedLogSection section(log, "FormatInfo", de::toString(*it));
+
+			// texture validity
+			if (isFormatFeatureSupported(ctxFormats, *it, TEXTURE_VALID))
+			{
+				tcu::MessageBuilder msg(&log);
+				msg << "* Valid texture format\n";
+
+				if (isFormatFeatureSupported(coreFormats, *it, TEXTURE_VALID))
+					msg << "\t* core feature";
+				else
+				{
+					msg << "\t* defined in supported extension(s):\n";
+					logAffectingExtensions("\t\t- ", ctxFormats, *it, TEXTURE_VALID, msg);
+				}
+
+				msg << tcu::TestLog::EndMessage;
+			}
+			else
+			{
+				tcu::MessageBuilder msg(&log);
+				msg << "* Unsupported texture format\n";
+
+				if (isFormatFeatureSupported(allFormats, *it, TEXTURE_VALID))
+				{
+					msg << "\t* requires any of the extensions or combinations:\n";
+					logAffectingExtensions("\t\t- ", allFormats, *it, TEXTURE_VALID, msg);
+				}
+				else
+					msg << "\t* no extension can make this format valid";
+
+				msg << tcu::TestLog::EndMessage;
+			}
+
+			// RBO validity
+			if (isFormatFeatureSupported(ctxFormats, *it, RENDERBUFFER_VALID))
+			{
+				tcu::MessageBuilder msg(&log);
+				msg << "* Valid renderbuffer format\n";
+
+				if (isFormatFeatureSupported(coreFormats, *it, RENDERBUFFER_VALID))
+					msg << "\t* core feature";
+				else
+				{
+					msg << "\t* defined in supported extension(s):\n";
+					logAffectingExtensions("\t\t- ", ctxFormats, *it, RENDERBUFFER_VALID, msg);
+				}
+
+				msg << tcu::TestLog::EndMessage;
+			}
+			else
+			{
+				tcu::MessageBuilder msg(&log);
+				msg << "* Unsupported renderbuffer format\n";
+
+				if (isFormatFeatureSupported(allFormats, *it, RENDERBUFFER_VALID))
+				{
+					msg << "\t* requires any of the extensions or combinations:\n";
+					logAffectingExtensions("\t\t- ", allFormats, *it, RENDERBUFFER_VALID, msg);
+				}
+				else
+					msg << "\t* no extension can make this format valid";
+
+				msg << tcu::TestLog::EndMessage;
+			}
+
+			// renderability
+			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_renderability); ++ndx)
+			{
+				if (isFormatFeatureSupported(ctxFormats, *it, s_renderability[ndx].flag | REQUIRED_RENDERABLE))
+				{
+					tcu::MessageBuilder msg(&log);
+					msg << "* Format is " << s_renderability[ndx].name << "\n";
+
+					if (isFormatFeatureSupported(coreFormats, *it, s_renderability[ndx].flag | REQUIRED_RENDERABLE))
+						msg << "\t* core feature";
+					else
+					{
+						msg << "\t* defined in supported extension(s):\n";
+						logAffectingExtensions("\t\t- ", ctxFormats, *it, s_renderability[ndx].flag | REQUIRED_RENDERABLE, msg);
+					}
+
+					msg << tcu::TestLog::EndMessage;
+				}
+				else if (isFormatFeatureSupported(ctxFormats, *it, s_renderability[ndx].flag))
+				{
+					tcu::MessageBuilder msg(&log);
+					msg << "* Format is allowed to be " << s_renderability[ndx].name << " but not required\n";
+
+					if (isFormatFeatureSupported(coreFormats, *it, s_renderability[ndx].flag))
+						msg << "\t* core feature";
+					else if (isFormatFeatureSupported(allFormats, *it, s_renderability[ndx].flag))
+					{
+						msg << "\t* extensions that would make format " << s_renderability[ndx].name << ":\n";
+						logAffectingExtensions("\t\t- ", allFormats, *it, s_renderability[ndx].flag, msg);
+					}
+					else
+						msg << "\t* no extension can make this format " << s_renderability[ndx].name;
+
+					msg << tcu::TestLog::EndMessage;
+				}
+				else
+				{
+					tcu::MessageBuilder msg(&log);
+					msg << "* Format is NOT " << s_renderability[ndx].name << "\n";
+
+					if (isFormatFeatureSupported(allFormats, *it, s_renderability[ndx].flag))
+					{
+						if (isFormatFeatureSupported(allFormats, *it, s_renderability[ndx].flag | REQUIRED_RENDERABLE))
+						{
+							msg << "\t* extensions that would make format " << s_renderability[ndx].name << ":\n";
+							logAffectingExtensions("\t\t- ", allFormats, *it, s_renderability[ndx].flag | REQUIRED_RENDERABLE, msg);
+						}
+						else
+						{
+							msg << "\t* extensions that are allowed to make format " << s_renderability[ndx].name << ":\n";
+							logAffectingExtensions("\t\t- ", allFormats, *it, s_renderability[ndx].flag, msg);
+						}
+					}
+					else
+						msg << "\t* no extension can make this format " << s_renderability[ndx].name;
+
+					msg << tcu::TestLog::EndMessage;
+				}
+			}
+		}
+	}
+}
+
 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);
+	glu::Framebuffer		fbo			(m_ctx.getRenderContext());
+	FboBuilder				builder		(*fbo, GL_FRAMEBUFFER, gl(*this));
+	const IterateResult		ret			= build(builder);
+	const ValidStatusCodes	reference	= m_ctx.getVerifier().validStatusCodes(builder);
+	const GLenum			errorCode	= builder.getError();
 
-	GLenum glStatus = builder.getError();
-	if (glStatus == GL_NO_ERROR)
-		glStatus = gl(*this).checkFramebufferStatus(GL_FRAMEBUFFER);
+	logFramebufferConfig(builder, m_testCtx.getLog());
+	logFormatInfo(builder, m_ctx.getCtxFormats(), m_ctx.getCoreFormats(), m_ctx.getAllFormats(), m_testCtx.getLog());
+	reference.logRules(m_testCtx.getLog());
+	reference.logLegalResults(m_testCtx.getLog());
 
 	// \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())
+	if (errorCode != GL_NO_ERROR)
 	{
-		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;
+		m_testCtx.getLog()
+			<< TestLog::Message
+			<< "Received " << glu::getErrorStr(errorCode) << " (during FBO initialization)."
+			<< 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");
+		if (reference.isErrorCodeValid(errorCode))
+			pass();
+		else if (reference.isErrorCodeRequired(GL_NO_ERROR))
+			fail(("Expected no error but got " + de::toString(glu::getErrorStr(errorCode))).c_str());
 		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.");
+			fail("Got wrong error code");
 	}
 	else
-		pass();
+	{
+		const GLenum	fboStatus	= gl(*this).checkFramebufferStatus(GL_FRAMEBUFFER);
+		const bool		validStatus	= reference.isFBOStatusValid(fboStatus);
+
+		m_testCtx.getLog()
+			<< TestLog::Message
+			<< "Received " << glu::getFramebufferStatusStr(fboStatus) << "."
+			<< TestLog::EndMessage;
+
+		if (!validStatus)
+		{
+			if (fboStatus == GL_FRAMEBUFFER_COMPLETE)
+				fail("Framebuffer checked as complete, expected incomplete");
+			else if (reference.isFBOStatusRequired(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 (fboStatus != GL_FRAMEBUFFER_COMPLETE && reference.isFBOStatusValid(GL_FRAMEBUFFER_COMPLETE))
+			qualityWarning("Framebuffer object could have checked as complete but did not.");
+		else
+			pass();
+	}
 
 	return ret;
 }
@@ -530,7 +696,7 @@
 
 	// 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) |
+	Formats formats = m_ctx.getCoreFormats().getFormats(formatFlag(attPoint) |
 														 formatFlag(bufType));
 	Formats::const_iterator it = formats.begin();
 	if (it == formats.end())
@@ -638,7 +804,7 @@
 {
 	GLenum				attPoint;
 	GLenum				bufType;
-	ImageFormat 		format;
+	ImageFormat			format;
 	static string		getName				(const RenderableParams& params)
 	{
 		return formatName(params.format);
@@ -709,7 +875,7 @@
 						: ParamTest<AttachmentParams> (group, params) {}
 
 protected:
-	IterateResult 	build				(FboBuilder& builder);
+	IterateResult	build				(FboBuilder& builder);
 	void			makeDepthAndStencil	(FboBuilder& builder);
 };
 
@@ -722,7 +888,7 @@
 		// image for both attachments.
 		const FormatFlags flags =
 			DEPTH_RENDERABLE | STENCIL_RENDERABLE | formatFlag(m_params.stencilKind);
-		const Formats& formats = m_ctx.getMinFormats().getFormats(flags);
+		const Formats& formats = m_ctx.getCoreFormats().getFormats(flags);
 		Formats::const_iterator it = formats.begin();
 		if (it != formats.end())
 		{
@@ -816,9 +982,9 @@
 		m_testCtx, "texture", "Tests for texture formats");
 
 	static const struct AttPoint {
-		GLenum 			attPoint;
-		const char* 	name;
-		const char* 	desc;
+		GLenum			attPoint;
+		const char*		name;
+		const char*		desc;
 	} attPoints[] =
 	{
 		{ GL_COLOR_ATTACHMENT0,		"color0",	"Tests for color attachments"	},
@@ -828,8 +994,8 @@
 
 	// 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);
+	const Formats rboFmts = m_allFormats.getFormats(ANY_FORMAT);
+	const Formats texFmts = m_allFormats.getFormats(ANY_FORMAT);
 
 	for (const AttPoint* it = DE_ARRAY_BEGIN(attPoints); it != DE_ARRAY_END(attPoints); it++)
 	{
diff --git a/modules/glshared/glsFboCompletenessTests.hpp b/modules/glshared/glsFboCompletenessTests.hpp
index b284731..bc3cd3b 100644
--- a/modules/glshared/glsFboCompletenessTests.hpp
+++ b/modules/glshared/glsFboCompletenessTests.hpp
@@ -63,8 +63,9 @@
 	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&			getCoreFormats			(void) const { return m_coreFormats; }
 	const FormatDB&			getCtxFormats			(void) const { return m_ctxFormats; }
+	const FormatDB&			getAllFormats			(void) const { return m_allFormats; }
 	bool					haveMultiColorAtts		(void) const { return m_haveMultiColorAtts; }
 	void					setHaveMulticolorAtts	(bool have) { m_haveMultiColorAtts = have; }
 	void					addFormats				(FormatEntries fmtRange);
@@ -75,9 +76,9 @@
 private:
 	TestContext&			m_testCtx;
 	RenderContext&			m_renderCtx;
-	FormatDB				m_minFormats;
+	FormatDB				m_coreFormats;
 	FormatDB				m_ctxFormats;
-	FormatDB				m_maxFormats;
+	FormatDB				m_allFormats;
 	FboVerifier				m_verifier;
 	bool					m_haveMultiColorAtts;
 };
diff --git a/modules/glshared/glsFboUtil.cpp b/modules/glshared/glsFboUtil.cpp
index 9d312a0..749d25e 100644
--- a/modules/glshared/glsFboUtil.cpp
+++ b/modules/glshared/glsFboUtil.cpp
@@ -28,6 +28,7 @@
 #include "gluTextureUtil.hpp"
 #include "gluStrUtil.hpp"
 #include "deStringUtil.hpp"
+#include "deSTLUtil.hpp"
 #include <sstream>
 
 using namespace glw;
@@ -63,18 +64,64 @@
 namespace FboUtil
 {
 
-
-void FormatDB::addFormat (ImageFormat format, FormatFlags newFlags)
+#if defined(DE_DEBUG)
+static bool isFramebufferStatus (glw::GLenum fboStatus)
 {
-	FormatFlags& flags = m_map[format];
+	return glu::getFramebufferStatusName(fboStatus) != DE_NULL;
+}
+
+static bool isErrorCode (glw::GLenum errorCode)
+{
+	return glu::getErrorName(errorCode) != DE_NULL;
+}
+#endif
+
+std::ostream& operator<< (std::ostream& stream, const ImageFormat& format)
+{
+	if (format.unsizedType == GL_NONE)
+	{
+		// sized format
+		return stream << glu::getPixelFormatStr(format.format);
+	}
+	else
+	{
+		// unsized format
+		return stream << "(format = " << glu::getPixelFormatStr(format.format) << ", type = " << glu::getTypeStr(format.unsizedType) << ")";
+	}
+}
+
+void FormatDB::addCoreFormat (ImageFormat format, FormatFlags newFlags)
+{
+	FormatFlags& flags = m_formatFlags[format];
 	flags = FormatFlags(flags | newFlags);
 }
 
+void FormatDB::addExtensionFormat (ImageFormat format, FormatFlags newFlags, const std::set<std::string>& requiredExtensions)
+{
+	DE_ASSERT(!requiredExtensions.empty());
+
+	{
+		FormatFlags& flags = m_formatFlags[format];
+		flags = FormatFlags(flags | newFlags);
+	}
+
+	{
+		std::set<ExtensionInfo>&	extensionInfo	= m_formatExtensions[format];
+		ExtensionInfo				extensionRecord;
+
+		extensionRecord.flags				= newFlags;
+		extensionRecord.requiredExtensions	= requiredExtensions;
+
+		DE_ASSERT(!de::contains(extensionInfo, extensionRecord)); // extensions specified only once
+		extensionInfo.insert(extensionRecord);
+	}
+}
+
 // 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++)
+	for (FormatMap::const_iterator it = m_formatFlags.begin(); it != m_formatFlags.end(); it++)
 	{
 		if ((it->second & requirements) == requirements)
 			ret.insert(it->first);
@@ -82,9 +129,94 @@
 	return ret;
 }
 
-FormatFlags FormatDB::getFormatInfo (ImageFormat format, FormatFlags fallback) const
+bool FormatDB::isKnownFormat (ImageFormat format) const
 {
-	return lookupDefault(m_map, format, fallback);
+	return de::contains(m_formatFlags, format);
+}
+
+FormatFlags FormatDB::getFormatInfo (ImageFormat format) const
+{
+	DE_ASSERT(de::contains(m_formatFlags, format));
+	return de::lookup(m_formatFlags, format);
+}
+
+std::set<std::set<std::string> > FormatDB::getFormatFeatureExtensions (ImageFormat format, FormatFlags requirements) const
+{
+	DE_ASSERT(de::contains(m_formatExtensions, format));
+
+	const std::set<ExtensionInfo>&		extensionInfo	= de::lookup(m_formatExtensions, format);
+	std::set<std::set<std::string> >	ret;
+
+	for (std::set<ExtensionInfo>::const_iterator it = extensionInfo.begin(); it != extensionInfo.end(); ++it)
+	{
+		if ((it->flags & requirements) == requirements)
+			ret.insert(it->requiredExtensions);
+	}
+
+	return ret;
+}
+
+bool FormatDB::ExtensionInfo::operator< (const ExtensionInfo& other) const
+{
+	return (requiredExtensions < other.requiredExtensions) ||
+		   ((requiredExtensions == other.requiredExtensions) && (flags < other.flags));
+}
+
+static bool detectGLESCompatibleContext (const RenderContext& ctx, int requiredMajor, int requiredMinor)
+{
+	const glw::Functions&	gl				= ctx.getFunctions();
+	glw::GLint				majorVersion	= 0;
+	glw::GLint				minorVersion	= 0;
+
+	// Detect compatible GLES context by querying GL_MAJOR_VERSION.
+	// This query does not exist on GLES2 so a failing query implies
+	// GLES2 context.
+
+	gl.getIntegerv(GL_MAJOR_VERSION, &majorVersion);
+	if (gl.getError() != GL_NO_ERROR)
+		majorVersion = 2;
+
+	gl.getIntegerv(GL_MINOR_VERSION, &minorVersion);
+	if (gl.getError() != GL_NO_ERROR)
+		minorVersion = 0;
+
+	return (majorVersion > requiredMajor) || (majorVersion == requiredMajor && minorVersion >= requiredMinor);
+}
+
+static bool checkExtensionSupport (const ContextInfo& ctxInfo, const RenderContext& ctx, const std::string& extension)
+{
+	if (de::beginsWith(extension, "GL_"))
+		return ctxInfo.isExtensionSupported(extension.c_str());
+	else if (extension == "DEQP_gles3_core_compatible")
+		return detectGLESCompatibleContext(ctx, 3, 0);
+	else if (extension == "DEQP_gles31_core_compatible")
+		return detectGLESCompatibleContext(ctx, 3, 1);
+	else
+	{
+		DE_ASSERT(false);
+		return false;
+	}
+}
+
+bool checkExtensionSupport (const RenderContext& ctx, const std::string& extension)
+{
+	const de::UniquePtr<ContextInfo> info(ContextInfo::create(ctx));
+	return checkExtensionSupport(*info, ctx, extension);
+}
+
+std::string getExtensionDescription (const std::string& extension)
+{
+	if (de::beginsWith(extension, "GL_"))
+		return extension;
+	else if (extension == "DEQP_gles3_core_compatible")
+		return "GLES3 compatible context";
+	else if (extension == "DEQP_gles31_core_compatible")
+		return "GLES3.1 compatible context";
+	else
+	{
+		DE_ASSERT(false);
+		return "";
+	}
 }
 
 void addFormats (FormatDB& db, FormatEntries stdFmts)
@@ -92,34 +224,46 @@
 	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);
+			db.addCoreFormat(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++)
+	for (const FormatExtEntry* entryIt = extFmts.begin(); entryIt != extFmts.end(); entryIt++)
 	{
-		bool supported = true;
-		if (ctxInfo)
+		bool					supported			= true;
+		std::set<std::string>	requiredExtensions;
+
+		// parse required extensions
 		{
-			istringstream tokenStream(string(it->extensions));
+			istringstream tokenStream(string(entryIt->extensions));
 			istream_iterator<string> tokens((tokenStream)), end;
 
 			while (tokens != end)
 			{
-				if (!ctxInfo->isExtensionSupported(tokens->c_str()))
+				requiredExtensions.insert(*tokens);
+				++tokens;
+			}
+		}
+
+		// check support
+		if (ctxInfo)
+		{
+			for (std::set<std::string>::const_iterator extIt = requiredExtensions.begin(); extIt != requiredExtensions.end(); ++extIt)
+			{
+				if (!checkExtensionSupport(*ctxInfo, *ctx, *extIt))
 				{
 					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));
+			for (const FormatKey* i2 = entryIt->formats.begin(); i2 != entryIt->formats.end(); i2++)
+				db.addExtensionFormat(formatKeyInfo(*i2), FormatFlags(entryIt->flags), requiredExtensions);
 	}
 }
 
@@ -143,6 +287,19 @@
 	}
 }
 
+static FormatFlags getAttachmentRenderabilityFlag (GLenum attachment)
+{
+	switch (attachment)
+	{
+		case GL_STENCIL_ATTACHMENT:			return STENCIL_RENDERABLE;
+		case GL_DEPTH_ATTACHMENT:			return DEPTH_RENDERABLE;
+
+		default:
+			DE_ASSERT(attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15);
+			return COLOR_RENDERABLE;
+	}
+}
+
 namespace config {
 
 GLsizei	imageNumSamples	(const Image& img)
@@ -332,45 +489,67 @@
 			// 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);
+			if (textureLayer(*texAtt) >= ltex->numLayers)
+				cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Attached layer index is larger than present");
 		}
 
 	// "The width and height of image are non-zero."
-	cctx.require(image->width > 0 && image->height > 0, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+	if (image->width == 0 || image->height == 0)
+		cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Width and height of an image are not non-zero");
 
 	// 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);
+	if (db.isKnownFormat(image->internalFormat))
+	{
+		const FormatFlags flags = db.getFormatInfo(image->internalFormat);
+
+		// If the format does not have the proper renderability flag, the
+		// completeness check _must_ fail.
+		if ((flags & getAttachmentRenderabilityFlag(attPoint)) == 0)
+			cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Attachment format is not renderable in this attachment");
+		// If the format is only optionally renderable, the completeness check _can_ fail.
+		else if ((flags & REQUIRED_RENDERABLE) == 0)
+			cctx.addPotentialFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Attachment format is not required renderable");
+	}
+	else
+		cctx.addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, "Attachment format is not legal");
 }
 
 } // namespace config
 
 using namespace config;
 
-void Checker::require (bool condition, GLenum error)
+Checker::Checker (const glu::RenderContext& ctx)
+	: m_renderCtx(ctx)
 {
-	if (!condition)
-	{
-		m_statusCodes.erase(GL_FRAMEBUFFER_COMPLETE);
-		m_statusCodes.insert(error);
-	}
+	m_statusCodes.setAllowComplete(true);
 }
 
-void Checker::canRequire (bool condition, GLenum error)
+void Checker::addGLError (glw::GLenum error, const char* description)
 {
-	if (!condition)
-		m_statusCodes.insert(error);
+	m_statusCodes.addErrorCode(error, description);
+	m_statusCodes.setAllowComplete(false);
 }
 
-FboVerifier::FboVerifier (const FormatDB& formats, CheckerFactory& factory)
-	: m_formats				(formats)
-	, m_factory				(factory)
+void Checker::addPotentialGLError (glw::GLenum error, const char* description)
+{
+	m_statusCodes.addErrorCode(error, description);
+}
+
+void Checker::addFBOStatus (GLenum status, const char* description)
+{
+	m_statusCodes.addFBOErrorStatus(status, description);
+	m_statusCodes.setAllowComplete(false);
+}
+
+void Checker::addPotentialFBOStatus (GLenum status, const char* description)
+{
+	m_statusCodes.addFBOErrorStatus(status, description);
+}
+
+FboVerifier::FboVerifier (const FormatDB& formats, CheckerFactory& factory, const glu::RenderContext& renderCtx)
+	: m_formats		(formats)
+	, m_factory		(factory)
+	, m_renderCtx	(renderCtx)
 {
 }
 
@@ -391,37 +570,71 @@
  * `glCheckFramebufferStatus` was ever called.
  *
  *//*--------------------------------------------------------------------*/
-StatusCodes FboVerifier::validStatusCodes (const Framebuffer& fboConfig) const
+ValidStatusCodes FboVerifier::validStatusCodes (const Framebuffer& fboConfig) const
 {
 	const AttachmentMap& atts = fboConfig.attachments;
-	const UniquePtr<Checker> cctx(m_factory.createChecker());
+	const UniquePtr<Checker> cctx(m_factory.createChecker(m_renderCtx));
 
 	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);
+		std::string errorDescription;
+
+		if (m_formats.isKnownFormat(it->second->internalFormat))
+		{
+			const FormatFlags flags = m_formats.getFormatInfo(it->second->internalFormat);
+
+			if ((flags & TEXTURE_VALID) == 0)
+				errorDescription = "Format " + de::toString(it->second->internalFormat) + " is not a valid format for a texture";
+		}
+		else if (it->second->internalFormat.unsizedType == GL_NONE)
+		{
+			// sized format
+			errorDescription = "Format " + de::toString(it->second->internalFormat) + " does not exist";
+		}
+		else
+		{
+			// unsized type-format pair
+			errorDescription = "Format " + de::toString(it->second->internalFormat) + " is not a legal format";
+		}
+
+		if (!errorDescription.empty())
+		{
+			cctx->addGLError(GL_INVALID_ENUM,		errorDescription.c_str());
+			cctx->addGLError(GL_INVALID_OPERATION,	errorDescription.c_str());
+			cctx->addGLError(GL_INVALID_VALUE,		errorDescription.c_str());
+		}
 	}
 
 	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);
+		if (m_formats.isKnownFormat(it->second->internalFormat))
+		{
+			const FormatFlags flags = m_formats.getFormatInfo(it->second->internalFormat);
+			if ((flags & RENDERBUFFER_VALID) == 0)
+			{
+				const std::string reason = "Format " + de::toString(it->second->internalFormat) + " is not a valid format for a renderbuffer";
+				cctx->addGLError(GL_INVALID_ENUM, reason.c_str());
+			}
+		}
+		else
+		{
+			const std::string reason = "Internal format " + de::toString(it->second->internalFormat) + " does not exist";
+			cctx->addGLError(GL_INVALID_ENUM, reason.c_str());
+		}
 	}
 
 	// "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);
+	// \todo support XXX_framebuffer_no_attachments
+	if (atts.empty())
+		cctx->addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, "No images attached to the framebuffer");
 
 	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);
 	}
@@ -443,9 +656,9 @@
 	switch (type)
 	{
 		case GL_TEXTURE:
-			return lookupDefault(textures, imgName, DE_NULL);
+			return de::lookupDefault(textures, imgName, DE_NULL);
 		case GL_RENDERBUFFER:
-			return lookupDefault(rbos, imgName, DE_NULL);
+			return de::lookupDefault(rbos, imgName, DE_NULL);
 		default:
 			DE_ASSERT(!"Bad image type");
 	}
@@ -473,7 +686,7 @@
 	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, "Width",				toString(img.width));
 	logField(log, "Height",				toString(img.height));
 }
 
@@ -520,32 +733,22 @@
 {
 	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				num			= toString(it->first);
+		const tcu::ScopedLogSection	subsection	(log, num, "Renderbuffer " + num);
 
-	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;
+		logRenderbuffer(*it->second, log);
 	}
-	log << TestLog::EndSection; // Textures
+
+	for (TextureMap::const_iterator it = cfg.textures.begin();
+		it != cfg.textures.end(); ++it)
+	{
+		const string				num			= toString(it->first);
+		const tcu::ScopedLogSection	subsection	(log, num, "Texture " + num);
+
+		logTexture(*it->second, log);
+	}
 
 	const string attDesc = cfg.attachments.empty()
 		? "Framebuffer has no attachments"
@@ -564,6 +767,166 @@
 	log << TestLog::EndSection; // Framebuffer
 }
 
+ValidStatusCodes::ValidStatusCodes (void)
+	: m_allowComplete(false)
+{
+}
+
+bool ValidStatusCodes::isFBOStatusValid (glw::GLenum fboStatus) const
+{
+	if (fboStatus == GL_FRAMEBUFFER_COMPLETE)
+		return m_allowComplete;
+	else
+	{
+		// rule violation exists?
+		for (int ndx = 0; ndx < (int)m_errorStatuses.size(); ++ndx)
+		{
+			if (m_errorStatuses[ndx].errorCode == fboStatus)
+				return true;
+		}
+		return false;
+	}
+}
+
+bool ValidStatusCodes::isFBOStatusRequired (glw::GLenum fboStatus) const
+{
+	if (fboStatus == GL_FRAMEBUFFER_COMPLETE)
+		return m_allowComplete && m_errorStatuses.empty();
+	else
+		// fboStatus is the only allowed error status and succeeding is forbidden
+		return !m_allowComplete && m_errorStatuses.size() == 1 && m_errorStatuses.front().errorCode == fboStatus;
+}
+
+bool ValidStatusCodes::isErrorCodeValid (glw::GLenum errorCode) const
+{
+	if (errorCode == GL_NO_ERROR)
+		return m_errorCodes.empty();
+	else
+	{
+		// rule violation exists?
+		for (int ndx = 0; ndx < (int)m_errorCodes.size(); ++ndx)
+		{
+			if (m_errorCodes[ndx].errorCode == errorCode)
+				return true;
+		}
+		return false;
+	}
+}
+
+bool ValidStatusCodes::isErrorCodeRequired (glw::GLenum errorCode) const
+{
+	if (m_errorCodes.empty() && errorCode == GL_NO_ERROR)
+		return true;
+	else
+		// only this error code listed
+		return m_errorCodes.size() == 1 && m_errorCodes.front().errorCode == errorCode;
+}
+
+void ValidStatusCodes::addErrorCode (glw::GLenum error, const char* description)
+{
+	DE_ASSERT(isErrorCode(error));
+	DE_ASSERT(error != GL_NO_ERROR);
+	addViolation(m_errorCodes, error, description);
+}
+
+void ValidStatusCodes::addFBOErrorStatus (glw::GLenum status, const char* description)
+{
+	DE_ASSERT(isFramebufferStatus(status));
+	DE_ASSERT(status != GL_FRAMEBUFFER_COMPLETE);
+	addViolation(m_errorStatuses, status, description);
+}
+
+void ValidStatusCodes::setAllowComplete (bool b)
+{
+	m_allowComplete = b;
+}
+
+void ValidStatusCodes::logLegalResults (tcu::TestLog& log) const
+{
+	tcu::MessageBuilder			msg				(&log);
+	std::vector<std::string>	validResults;
+
+	for (int ndx = 0; ndx < (int)m_errorCodes.size(); ++ndx)
+		validResults.push_back(std::string(glu::getErrorName(m_errorCodes[ndx].errorCode)) + " (during FBO initialization)");
+
+	for (int ndx = 0; ndx < (int)m_errorStatuses.size(); ++ndx)
+		validResults.push_back(glu::getFramebufferStatusName(m_errorStatuses[ndx].errorCode));
+
+	if (m_allowComplete)
+		validResults.push_back("GL_FRAMEBUFFER_COMPLETE");
+
+	msg << "Expected ";
+	if (validResults.size() > 1)
+		msg << "one of ";
+
+	for (int ndx = 0; ndx < (int)validResults.size(); ++ndx)
+	{
+		const bool last			= ((ndx + 1) == (int)validResults.size());
+		const bool secondToLast	= ((ndx + 2) == (int)validResults.size());
+
+		msg << validResults[ndx];
+		if (!last)
+			msg << ((secondToLast) ? (" or ") : (", "));
+	}
+
+	msg << "." << TestLog::EndMessage;
+}
+
+void ValidStatusCodes::logRules (tcu::TestLog& log) const
+{
+	const tcu::ScopedLogSection section(log, "Rules", "Active rules");
+
+	for (int ndx = 0; ndx < (int)m_errorCodes.size(); ++ndx)
+		logRule(log, glu::getErrorName(m_errorCodes[ndx].errorCode), m_errorCodes[ndx].rules);
+
+	for (int ndx = 0; ndx < (int)m_errorStatuses.size(); ++ndx)
+		logRule(log, glu::getFramebufferStatusName(m_errorStatuses[ndx].errorCode), m_errorStatuses[ndx].rules);
+
+	if (m_allowComplete)
+	{
+		std::set<std::string> defaultRule;
+		defaultRule.insert("FBO is complete");
+		logRule(log, "GL_FRAMEBUFFER_COMPLETE", defaultRule);
+	}
+}
+
+void ValidStatusCodes::logRule (tcu::TestLog& log, const std::string& ruleName, const std::set<std::string>& rules) const
+{
+	if (!rules.empty())
+	{
+		const tcu::ScopedLogSection		section	(log, ruleName, ruleName);
+		tcu::MessageBuilder				msg		(&log);
+
+		msg << "Rules:\n";
+		for (std::set<std::string>::const_iterator it = rules.begin(); it != rules.end(); ++it)
+			msg << "\t * " << *it << "\n";
+		msg << TestLog::EndMessage;
+	}
+}
+
+void ValidStatusCodes::addViolation (std::vector<RuleViolation>& dst, glw::GLenum code, const char* description) const
+{
+	// rule violation already exists?
+	for (int ndx = 0; ndx < (int)dst.size(); ++ndx)
+	{
+		if (dst[ndx].errorCode == code)
+		{
+			dst[ndx].rules.insert(std::string(description));
+			return;
+		}
+	}
+
+	// new violation
+	{
+		RuleViolation violation;
+
+		violation.errorCode = code;
+		violation.rules.insert(std::string(description));
+
+		dst.push_back(violation);
+	}
+}
+
 FboBuilder::FboBuilder (GLuint fbo, GLenum target, const glw::Functions& gl)
 	: m_error	(GL_NO_ERROR)
 	, m_target	(target)
diff --git a/modules/glshared/glsFboUtil.hpp b/modules/glshared/glsFboUtil.hpp
index 00887a8..6affa68 100644
--- a/modules/glshared/glsFboUtil.hpp
+++ b/modules/glshared/glsFboUtil.hpp
@@ -44,8 +44,6 @@
 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?
@@ -76,71 +74,6 @@
 	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
 {
 
@@ -163,7 +96,7 @@
 	//! Type if format is unsized, GL_NONE if sized.
 	glw::GLenum				unsizedType;
 
-	bool 					operator<		(const ImageFormat& other) const
+	bool					operator<		(const ImageFormat& other) const
 	{
 		return (format < other.format ||
 				(format == other.format && unsizedType < other.unsizedType));
@@ -176,6 +109,8 @@
 	}
 };
 
+std::ostream& operator<< (std::ostream& stream, const ImageFormat& format);
+
 static inline ImageFormat formatKeyInfo(FormatKey key)
 {
 	ImageFormat fmt = { key & 0xffff, key >> 16 };
@@ -205,15 +140,28 @@
 class FormatDB
 {
 public:
-	void							addFormat		(ImageFormat format, FormatFlags flags);
-	Formats							getFormats		(FormatFlags requirements) const;
-	FormatFlags						getFormatInfo	(ImageFormat format,
-													 FormatFlags fallback) const;
+	void								addCoreFormat				(ImageFormat format, FormatFlags flags);
+	void								addExtensionFormat			(ImageFormat format, FormatFlags flags, const std::set<std::string>& requiredExtensions);
+
+	Formats								getFormats					(FormatFlags requirements) const;
+	bool								isKnownFormat				(ImageFormat format) const;
+	FormatFlags							getFormatInfo				(ImageFormat format) const;
+	std::set<std::set<std::string> >	getFormatFeatureExtensions	(ImageFormat format, FormatFlags requirements) const;
 
 private:
-	typedef std::map<ImageFormat, FormatFlags>		FormatMap;
+	struct ExtensionInfo
+	{
+		FormatFlags					flags;
+		std::set<std::string>		requiredExtensions;
 
-	FormatMap						m_map;
+		bool						operator<			(const ExtensionInfo& other) const;
+	};
+
+	typedef std::map<ImageFormat, FormatFlags>					FormatMap;
+	typedef std::map<ImageFormat, std::set<ExtensionInfo> >		FormatExtensionMap;
+
+	FormatMap							m_formatFlags;
+	FormatExtensionMap					m_formatExtensions;
 };
 
 typedef Pair<FormatFlags, FormatKeys>				FormatEntry;
@@ -233,17 +181,22 @@
 
 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);
+// Check support for GL_* and DEQP_* extensions
+bool				checkExtensionSupport		(const glu::RenderContext& ctx, const std::string& extension);
+
+// Accepts GL_* and DEQP_* extension strings and converts DEQP_* strings to a human readable string
+std::string			getExtensionDescription		(const std::string& extensionName);
+
+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) {};
+	virtual						~Config			(void) {};
 };
 
 struct Image : public Config
@@ -270,7 +223,7 @@
 {
 							Texture			(void) : numLevels(1) {}
 
-	glw::GLint 				numLevels;
+	glw::GLint				numLevels;
 };
 
 struct TextureFlat : public Texture
@@ -303,8 +256,8 @@
 {
 							Attachment		(void) : target(GL_FRAMEBUFFER), imageName(0) {}
 
-	glw::GLenum 			target;
-	glw::GLuint 			imageName;
+	glw::GLenum				target;
+	glw::GLuint				imageName;
 
 	//! Returns `true` iff this attachment is "framebuffer attachment
 	//! complete" when bound to attachment point `attPoint`, and the current
@@ -370,8 +323,6 @@
 
 } // config
 
-void logFramebufferConfig(const config::Framebuffer& cfg, tcu::TestLog& log);
-
 class FboBuilder : public config::Framebuffer
 {
 public:
@@ -406,27 +357,67 @@
 	Configs						m_configs;
 };
 
-typedef std::set<glw::GLenum> StatusCodes;
+struct ValidStatusCodes
+{
+								ValidStatusCodes		(void);
+
+	bool						isFBOStatusValid		(glw::GLenum fboStatus) const;
+	bool						isFBOStatusRequired		(glw::GLenum fboStatus) const;
+	bool						isErrorCodeValid		(glw::GLenum errorCode) const;
+	bool						isErrorCodeRequired		(glw::GLenum errorCode) const;
+
+	void						addErrorCode			(glw::GLenum error, const char* description);
+	void						addFBOErrorStatus		(glw::GLenum status, const char* description);
+	void						setAllowComplete		(bool);
+
+	void						logLegalResults			(tcu::TestLog& log) const;
+	void						logRules				(tcu::TestLog& log) const;
+
+private:
+	struct RuleViolation
+	{
+		glw::GLenum				errorCode;
+		std::set<std::string>	rules;
+	};
+
+	void						logRule					(tcu::TestLog& log, const std::string& ruleName, const std::set<std::string>& rules) const;
+	void						addViolation			(std::vector<RuleViolation>& dst, glw::GLenum code, const char* description) const;
+
+	std::vector<RuleViolation>	m_errorCodes;			//!< Allowed GL errors, GL_NO_ERROR is not allowed
+	std::vector<RuleViolation>	m_errorStatuses;		//!< Allowed FBO error statuses, GL_FRAMEBUFFER_COMPLETE is not allowed
+	bool						m_allowComplete;		//!< true if (GL_NO_ERROR && GL_FRAMEBUFFER_COMPLETE) is allowed
+};
+
+void logFramebufferConfig (const config::Framebuffer& cfg, tcu::TestLog& log);
 
 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:
+								Checker					(const glu::RenderContext&);
+	virtual						~Checker				(void) {}
 
-	StatusCodes		m_statusCodes;	//< Allowed return values for glCheckFramebufferStatus.
+	void						addGLError				(glw::GLenum error, const char* description);
+	void						addPotentialGLError		(glw::GLenum error, const char* description);
+	void						addFBOStatus			(glw::GLenum status, const char* description);
+	void						addPotentialFBOStatus	(glw::GLenum status, const char* description);
+
+	ValidStatusCodes			getStatusCodes			(void) { return m_statusCodes; }
+
+	virtual void				check					(glw::GLenum				attPoint,
+														 const config::Attachment&	att,
+														 const config::Image*		image) = 0;
+
+protected:
+	const glu::RenderContext&	m_renderCtx;
+
+private:
+	ValidStatusCodes			m_statusCodes;	//< Allowed return values for glCheckFramebufferStatus.
 };
 
 class CheckerFactory
 {
 public:
-	virtual Checker*	createChecker	(void) = 0;
+	virtual Checker*	createChecker	(const glu::RenderContext&) = 0;
 };
 
 typedef std::set<glw::GLenum> AttachmentPoints;
@@ -435,14 +426,16 @@
 class FboVerifier
 {
 public:
-								FboVerifier				(const FormatDB& formats,
-														 CheckerFactory& factory);
+								FboVerifier				(const FormatDB&			formats,
+														 CheckerFactory&			factory,
+														 const glu::RenderContext&	renderCtx);
 
-	StatusCodes					validStatusCodes		(const config::Framebuffer& cfg) const;
+	ValidStatusCodes			validStatusCodes		(const config::Framebuffer& cfg) const;
 
 private:
 	const FormatDB&				m_formats;
 	CheckerFactory&				m_factory;
+	const glu::RenderContext&	m_renderCtx;
 };
 
 } // FboUtil