Merge "Improve logging in fbo completeness tests."
diff --git a/modules/gles2/functional/es2fFboCompletenessTests.cpp b/modules/gles2/functional/es2fFboCompletenessTests.cpp
index 5a67f8d..49c52de 100644
--- a/modules/gles2/functional/es2fFboCompletenessTests.cpp
+++ b/modules/gles2/functional/es2fFboCompletenessTests.cpp
@@ -147,8 +147,8 @@
 	}
 	else
 	{
-		require(image->width == m_width && image->height == m_height,
-				GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
+		if (image->width != m_width || image->height != m_height)
+			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 +157,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 +204,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 +214,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);
diff --git a/modules/gles3/functional/es3fFboCompletenessTests.cpp b/modules/gles3/functional/es3fFboCompletenessTests.cpp
index b392f51..858da35 100644
--- a/modules/gles3/functional/es3fFboCompletenessTests.cpp
+++ b/modules/gles3/functional/es3fFboCompletenessTests.cpp
@@ -191,16 +191,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 +212,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");
+		}
 	}
 }
 
diff --git a/modules/glshared/glsFboCompletenessTests.cpp b/modules/glshared/glsFboCompletenessTests.cpp
index fc93f49..9627198 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.
 
@@ -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 << extName;
+			extName = *it++;
+			msg << (it == requiredExtensions.end() ? " and " : ", ");
+		}
+
+		msg << 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..76c1c6d 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,37 @@
 	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));
 }
 
 void addFormats (FormatDB& db, FormatEntries stdFmts)
@@ -92,34 +167,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 (!ctxInfo->isExtensionSupported(extIt->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));
+			for (const FormatKey* i2 = entryIt->formats.begin(); i2 != entryIt->formats.end(); i2++)
+				db.addExtensionFormat(formatKeyInfo(*i2), FormatFlags(entryIt->flags), requiredExtensions);
 	}
 }
 
@@ -143,6 +230,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,40 +432,60 @@
 			// 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 (void)
 {
-	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);
+}
+
+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)
@@ -391,7 +511,7 @@
  * `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());
@@ -399,29 +519,63 @@
 	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 +597,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 +627,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 +674,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 +708,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..c6a14e7 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;
@@ -234,7 +182,7 @@
 typedef Range<FormatExtEntry>						FormatExtEntries;
 
 void				addFormats			(FormatDB& db, FormatEntries stdFmts);
-void 				addExtFormats		(FormatDB& db, FormatExtEntries extFmts,
+void				addExtFormats		(FormatDB& db, FormatExtEntries extFmts,
 										 const glu::RenderContext* ctx);
 glu::TransferFormat	transferImageFormat	(const ImageFormat& imgFormat);
 
@@ -243,7 +191,7 @@
 
 struct Config
 {
-	virtual 					~Config			(void) {};
+	virtual						~Config			(void) {};
 };
 
 struct Image : public Config
@@ -270,7 +218,7 @@
 {
 							Texture			(void) : numLevels(1) {}
 
-	glw::GLint 				numLevels;
+	glw::GLint				numLevels;
 };
 
 struct TextureFlat : public Texture
@@ -303,8 +251,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 +318,6 @@
 
 } // config
 
-void logFramebufferConfig(const config::Framebuffer& cfg, tcu::TestLog& log);
-
 class FboBuilder : public config::Framebuffer
 {
 public:
@@ -406,21 +352,58 @@
 	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;
+						Checker					(void);
+	virtual				~Checker				(void) {}
+
+	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;
 private:
 
-	StatusCodes		m_statusCodes;	//< Allowed return values for glCheckFramebufferStatus.
+	ValidStatusCodes	m_statusCodes;	//< Allowed return values for glCheckFramebufferStatus.
 };
 
 class CheckerFactory
@@ -438,7 +421,7 @@
 								FboVerifier				(const FormatDB& formats,
 														 CheckerFactory& factory);
 
-	StatusCodes					validStatusCodes		(const config::Framebuffer& cfg) const;
+	ValidStatusCodes			validStatusCodes		(const config::Framebuffer& cfg) const;
 
 private:
 	const FormatDB&				m_formats;