C++程序  |  999行  |  29.75 KB

/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) Module
 * -----------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Utilities for framebuffer objects.
 *//*--------------------------------------------------------------------*/

#include "glsFboUtil.hpp"

#include "glwEnums.hpp"
#include "deUniquePtr.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "deStringUtil.hpp"
#include "deSTLUtil.hpp"
#include <sstream>

using namespace glw;
using tcu::TestLog;
using tcu::TextureFormat;
using tcu::NotSupportedError;
using glu::TransferFormat;
using glu::mapGLInternalFormat;
using glu::mapGLTransferFormat;
using glu::getTextureFormatName;
using glu::getTypeName;
using glu::getFramebufferTargetName;
using glu::getFramebufferAttachmentName;
using glu::getFramebufferAttachmentTypeName;
using glu::getTextureTargetName;
using glu::getTransferFormat;
using glu::ContextInfo;
using glu::ContextType;
using glu::RenderContext;
using de::UniquePtr;
using de::toString;
using std::set;
using std::vector;
using std::string;
using std::istringstream;
using std::istream_iterator;

namespace deqp
{
namespace gls
{

namespace FboUtil
{

#if defined(DE_DEBUG)
static bool isFramebufferStatus (glw::GLenum fboStatus)
{
	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::getTextureFormatStr(format.format);
	}
	else
	{
		// unsized format
		return stream << "(format = " << glu::getTextureFormatStr(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_formatFlags.begin(); it != m_formatFlags.end(); it++)
	{
		if ((it->second & requirements) == requirements)
			ret.insert(it->first);
	}
	return ret;
}

bool FormatDB::isKnownFormat (ImageFormat format) const
{
	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)
{
	for (const FormatEntry* it = stdFmts.begin(); it != stdFmts.end(); it++)
	{
		for (const FormatKey* it2 = it->second.begin(); it2 != it->second.end(); it2++)
			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* entryIt = extFmts.begin(); entryIt != extFmts.end(); entryIt++)
	{
		bool					supported			= true;
		std::set<std::string>	requiredExtensions;

		// parse required extensions
		{
			istringstream tokenStream(string(entryIt->extensions));
			istream_iterator<string> tokens((tokenStream)), end;

			while (tokens != end)
			{
				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;
				}
			}
		}

		if (supported)
			for (const FormatKey* i2 = entryIt->formats.begin(); i2 != entryIt->formats.end(); i2++)
				db.addExtensionFormat(formatKeyInfo(*i2), FormatFlags(entryIt->flags), requiredExtensions);
	}
}

FormatFlags formatFlag (GLenum context)
{
	switch (context)
	{
		case GL_NONE:
			return FormatFlags(0);
		case GL_RENDERBUFFER:
			return RENDERBUFFER_VALID;
		case GL_TEXTURE:
			return TEXTURE_VALID;
		case GL_STENCIL_ATTACHMENT:
			return STENCIL_RENDERABLE;
		case GL_DEPTH_ATTACHMENT:
			return DEPTH_RENDERABLE;
		default:
			DE_ASSERT(context >= GL_COLOR_ATTACHMENT0 && context <= GL_COLOR_ATTACHMENT15);
			return COLOR_RENDERABLE;
	}
}

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)
{
	if (const Renderbuffer* rbo = dynamic_cast<const Renderbuffer*>(&img))
		return rbo->numSamples;
	return 0;
}

static GLenum glTarget (const Image& img)
{
	if (dynamic_cast<const Renderbuffer*>(&img) != DE_NULL)
		return GL_RENDERBUFFER;
	if (dynamic_cast<const Texture2D*>(&img) != DE_NULL)
		return GL_TEXTURE_2D;
	if (dynamic_cast<const TextureCubeMap*>(&img) != DE_NULL)
		return GL_TEXTURE_CUBE_MAP;
	if (dynamic_cast<const Texture3D*>(&img) != DE_NULL)
		return GL_TEXTURE_3D;
	if (dynamic_cast<const Texture2DArray*>(&img) != DE_NULL)
		return GL_TEXTURE_2D_ARRAY;

	DE_FATAL("Impossible image type");
	return GL_NONE;
}

static void glInitFlat (const TextureFlat& cfg, GLenum target, const glw::Functions& gl)
{
	const TransferFormat format = transferImageFormat(cfg.internalFormat);
	GLint w = cfg.width;
	GLint h = cfg.height;
	for (GLint level = 0; level < cfg.numLevels; level++)
	{
		gl.texImage2D(target, level, cfg.internalFormat.format, w, h, 0,
					  format.format, format.dataType, DE_NULL);
		w = de::max(1, w / 2);
		h = de::max(1, h / 2);
	}
}

static void glInitLayered (const TextureLayered& cfg,
						   GLint depth_divider, const glw::Functions& gl)
{
	const TransferFormat format = transferImageFormat(cfg.internalFormat);
	GLint w = cfg.width;
	GLint h = cfg.height;
	GLint depth = cfg.numLayers;
	for (GLint level = 0; level < cfg.numLevels; level++)
	{
		gl.texImage3D(glTarget(cfg), level, cfg.internalFormat.format, w, h, depth, 0,
					  format.format, format.dataType, DE_NULL);
		w = de::max(1, w / 2);
		h = de::max(1, h / 2);
		depth = de::max(1, depth / depth_divider);
	}
}

static void glInit (const Texture& cfg, const glw::Functions& gl)
{
	if (const Texture2D* t2d = dynamic_cast<const Texture2D*>(&cfg))
		glInitFlat(*t2d, glTarget(*t2d), gl);
	else if (const TextureCubeMap* tcm = dynamic_cast<const TextureCubeMap*>(&cfg))
	{
		// \todo [2013-12-05 lauri]
		// move this to glu or someplace sensible (this array is already
		// present in duplicates)
		static const GLenum s_cubeMapFaces[] =
			{
				GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
				GL_TEXTURE_CUBE_MAP_POSITIVE_X,
				GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
				GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
				GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
				GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
			};
		const Range<GLenum> range = GLS_ARRAY_RANGE(s_cubeMapFaces);
		for (const GLenum* it = range.begin(); it != range.end(); it++)
			glInitFlat(*tcm, *it, gl);
	}
	else if (const Texture3D* t3d = dynamic_cast<const Texture3D*>(&cfg))
		glInitLayered(*t3d, 2, gl);
	else if (const Texture2DArray* t2a = dynamic_cast<const Texture2DArray*>(&cfg))
		glInitLayered(*t2a, 1, gl);
}

static GLuint glCreate (const Image& cfg, const glw::Functions& gl)
{
	GLuint ret = 0;
	if (const Renderbuffer* const rbo = dynamic_cast<const Renderbuffer*>(&cfg))
	{
		gl.genRenderbuffers(1, &ret);
		gl.bindRenderbuffer(GL_RENDERBUFFER, ret);

		if (rbo->numSamples == 0)
			gl.renderbufferStorage(GL_RENDERBUFFER, rbo->internalFormat.format,
								   rbo->width, rbo->height);
		else
			gl.renderbufferStorageMultisample(
				GL_RENDERBUFFER, rbo->numSamples, rbo->internalFormat.format,
				rbo->width, rbo->height);

		gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
	}
	else if (const Texture* const tex = dynamic_cast<const Texture*>(&cfg))
	{
		gl.genTextures(1, &ret);
		gl.bindTexture(glTarget(*tex), ret);
		glInit(*tex, gl);
		gl.bindTexture(glTarget(*tex), 0);
	}
	else
		DE_FATAL("Impossible image type");
	return ret;
}

static void glDelete (const Image& cfg, GLuint img, const glw::Functions& gl)
{
	if (dynamic_cast<const Renderbuffer*>(&cfg) != DE_NULL)
		gl.deleteRenderbuffers(1, &img);
	else if (dynamic_cast<const Texture*>(&cfg) != DE_NULL)
		gl.deleteTextures(1, &img);
	else
		DE_FATAL("Impossible image type");
}

static void attachAttachment (const Attachment& att, GLenum attPoint,
							  const glw::Functions& gl)
{
	if (const RenderbufferAttachment* const rAtt =
		dynamic_cast<const RenderbufferAttachment*>(&att))
		gl.framebufferRenderbuffer(rAtt->target, attPoint,
								   rAtt->renderbufferTarget, rAtt->imageName);
	else if (const TextureFlatAttachment* const fAtt =
			 dynamic_cast<const TextureFlatAttachment*>(&att))
		gl.framebufferTexture2D(fAtt->target, attPoint,
								fAtt->texTarget, fAtt->imageName, fAtt->level);
	else if (const TextureLayerAttachment* const lAtt =
			 dynamic_cast<const TextureLayerAttachment*>(&att))
		gl.framebufferTextureLayer(lAtt->target, attPoint,
								   lAtt->imageName, lAtt->level, lAtt->layer);
	else
		DE_FATAL("Impossible attachment type");
}

GLenum attachmentType (const Attachment& att)
{
	if (dynamic_cast<const RenderbufferAttachment*>(&att) != DE_NULL)
		return GL_RENDERBUFFER;
	else if (dynamic_cast<const TextureAttachment*>(&att) != DE_NULL)
		return GL_TEXTURE;

	DE_FATAL("Impossible attachment type");
	return GL_NONE;
}

static GLsizei textureLayer (const TextureAttachment& tAtt)
{
	if (dynamic_cast<const TextureFlatAttachment*>(&tAtt) != DE_NULL)
		return 0;
	else if (const TextureLayerAttachment* const lAtt =
			 dynamic_cast<const TextureLayerAttachment*>(&tAtt))
		return lAtt->layer;

	DE_FATAL("Impossible attachment type");
	return 0;
}

static void checkAttachmentCompleteness (Checker& cctx, const Attachment& attachment,
										 GLenum attPoint, const Image* image,
										 const FormatDB& db)
{
	// GLES2 4.4.5 / GLES3 4.4.4, "Framebuffer attachment completeness"

	if (const TextureAttachment* const texAtt =
		dynamic_cast<const TextureAttachment*>(&attachment))
		if (const TextureLayered* const ltex = dynamic_cast<const TextureLayered*>(image))
		{
			// GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is
			// TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a
			// three-dimensional texture, then the value of
			// FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the depth
			// of the texture.
			//
			// GLES3: "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is
			// TEXTURE and the value of FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names a
			// two-dimensional array texture, then the value of
			// FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER must be smaller than the
			// number of layers in the texture.

			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."
	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
	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;

Checker::Checker (const glu::RenderContext& ctx)
	: m_renderCtx(ctx)
{
	m_statusCodes.setAllowComplete(true);
}

void Checker::addGLError (glw::GLenum error, const char* description)
{
	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, const glu::RenderContext& renderCtx)
	: m_formats		(formats)
	, m_factory		(factory)
	, m_renderCtx	(renderCtx)
{
}

/*--------------------------------------------------------------------*//*!
 * \brief Return acceptable framebuffer status codes.
 *
 * This function examines the framebuffer configuration descriptor `fboConfig`
 * and returns the set of status codes that `glCheckFramebufferStatus` is
 * allowed to return on a conforming implementation when given a framebuffer
 * whose configuration adheres to `fboConfig`.
 *
 * The returned set is guaranteed to be non-empty, but it may contain multiple
 * INCOMPLETE statuses (if there are multiple errors in the spec), or or a mix
 * of COMPLETE and INCOMPLETE statuses (if supporting a FBO with this spec is
 * optional). Furthermore, the statuses may contain GL error codes, which
 * indicate that trying to create a framebuffer configuration like this could
 * have failed with an error (if one was checked for) even before
 * `glCheckFramebufferStatus` was ever called.
 *
 *//*--------------------------------------------------------------------*/
ValidStatusCodes FboVerifier::validStatusCodes (const Framebuffer& fboConfig) const
{
	const AttachmentMap& atts = fboConfig.attachments;
	const UniquePtr<Checker> cctx(m_factory.createChecker(m_renderCtx));

	for (TextureMap::const_iterator it = fboConfig.textures.begin();
		 it != fboConfig.textures.end(); it++)
	{
		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++)
	{
		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
	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);
	}

	return cctx->getStatusCodes();
}


void Framebuffer::attach (glw::GLenum attPoint, const Attachment* att)
{
	if (att == DE_NULL)
		attachments.erase(attPoint);
	else
		attachments[attPoint] = att;
}

const Image* Framebuffer::getImage (GLenum type, glw::GLuint imgName) const
{
	switch (type)
	{
		case GL_TEXTURE:
			return de::lookupDefault(textures, imgName, DE_NULL);
		case GL_RENDERBUFFER:
			return de::lookupDefault(rbos, imgName, DE_NULL);
		default:
			DE_FATAL("Bad image type");
	}
	return DE_NULL; // shut up compiler warning
}

void Framebuffer::setTexture (glw::GLuint texName, const Texture& texCfg)
{
	textures[texName] = &texCfg;
}

void Framebuffer::setRbo (glw::GLuint rbName, const Renderbuffer& rbCfg)
{
	rbos[rbName] = &rbCfg;
}

static void logField (TestLog& log, const string& field, const string& value)
{
	log << TestLog::Message << field << ": " << value << TestLog::EndMessage;
}

static void logImage (const Image& img, TestLog& log, bool useType)
{
	const GLenum type = img.internalFormat.unsizedType;
	logField(log, "Internal format",	getTextureFormatName(img.internalFormat.format));
	if (useType && type != GL_NONE)
		logField(log, "Format type",	getTypeName(type));
	logField(log, "Width",				toString(img.width));
	logField(log, "Height",				toString(img.height));
}

static void logRenderbuffer (const Renderbuffer& rbo, TestLog& log)
{
	logImage(rbo, log, false);
	logField(log, "Samples",			toString(rbo.numSamples));
}

static void logTexture (const Texture& tex, TestLog& log)
{
	logField(log, "Type",				glu::getTextureTargetName(glTarget(tex)));
	logImage(tex, log, true);
	logField(log, "Levels",				toString(tex.numLevels));
	if (const TextureLayered* const lTex = dynamic_cast<const TextureLayered*>(&tex))
		logField(log, "Layers",				toString(lTex->numLayers));
}

static void logAttachment (const Attachment& att, TestLog& log)
{
	logField(log, "Target",				getFramebufferTargetName(att.target));
	logField(log, "Type",				getFramebufferAttachmentTypeName(attachmentType(att)));
	logField(log, "Image Name",			toString(att.imageName));
	if (const RenderbufferAttachment* const rAtt
		= dynamic_cast<const RenderbufferAttachment*>(&att))
	{
		DE_UNREF(rAtt); // To shut up compiler during optimized builds.
		DE_ASSERT(rAtt->renderbufferTarget == GL_RENDERBUFFER);
		logField(log, "Renderbuffer Target",	"GL_RENDERBUFFER");
	}
	else if (const TextureAttachment* const tAtt = dynamic_cast<const TextureAttachment*>(&att))
	{
		logField(log, "Mipmap Level",		toString(tAtt->level));
		if (const TextureFlatAttachment* const fAtt =
			dynamic_cast<const TextureFlatAttachment*>(tAtt))
			logField(log, "Texture Target",		getTextureTargetName(fAtt->texTarget));
		else if (const TextureLayerAttachment* const lAtt =
			dynamic_cast<const TextureLayerAttachment*>(tAtt))
			logField(log, "Layer",				toString(lAtt->level));
	}
}

void logFramebufferConfig (const Framebuffer& cfg, TestLog& log)
{
	log << TestLog::Section("Framebuffer", "Framebuffer configuration");

	for (RboMap::const_iterator it = cfg.rbos.begin(); it != cfg.rbos.end(); ++it)
	{
		const string				num			= toString(it->first);
		const tcu::ScopedLogSection	subsection	(log, num, "Renderbuffer " + num);

		logRenderbuffer(*it->second, log);
	}

	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"
		: "Framebuffer attachments";
	log << TestLog::Section("Attachments", attDesc);
	for (AttachmentMap::const_iterator it = cfg.attachments.begin();
		 it != cfg.attachments.end(); it++)
	{
		const string attPointName = getFramebufferAttachmentName(it->first);
		log << TestLog::Section(attPointName, "Attachment point " + attPointName);
		logAttachment(*it->second, log);
		log << TestLog::EndSection;
	}
	log << TestLog::EndSection; // Attachments

	log << TestLog::EndSection; // Framebuffer
}

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)
	, m_gl		(gl)
{
	m_gl.bindFramebuffer(m_target, fbo);
}

FboBuilder::~FboBuilder (void)
{
	for (TextureMap::const_iterator it = textures.begin(); it != textures.end(); it++)
	{
		glDelete(*it->second, it->first, m_gl);
	}
	for (RboMap::const_iterator it = rbos.begin(); it != rbos.end(); it++)
	{
		glDelete(*it->second, it->first, m_gl);
	}
	m_gl.bindFramebuffer(m_target, 0);
	for (Configs::const_iterator it = m_configs.begin(); it != m_configs.end(); it++)
	{
		delete *it;
	}
}

void FboBuilder::checkError (void)
{
	const GLenum error = m_gl.getError();
	if (error != GL_NO_ERROR && m_error == GL_NO_ERROR)
		m_error = error;
}

void FboBuilder::glAttach (GLenum attPoint, const Attachment* att)
{
	if (att == NULL)
		m_gl.framebufferRenderbuffer(m_target, attPoint, GL_RENDERBUFFER, 0);
	else
		attachAttachment(*att, attPoint, m_gl);
	checkError();
	attach(attPoint, att);
}

GLuint FboBuilder::glCreateTexture (const Texture& texCfg)
{
	const GLuint texName = glCreate(texCfg, m_gl);
	checkError();
	setTexture(texName, texCfg);
	return texName;
}

GLuint FboBuilder::glCreateRbo (const Renderbuffer& rbCfg)
{
	const GLuint rbName = glCreate(rbCfg, m_gl);
	checkError();
	setRbo(rbName, rbCfg);
	return rbName;
}

TransferFormat transferImageFormat (const ImageFormat& imgFormat)
{
	if (imgFormat.unsizedType == GL_NONE)
		return getTransferFormat(mapGLInternalFormat(imgFormat.format));
	else
		return TransferFormat(imgFormat.format, imgFormat.unsizedType);
}

} // FboUtil
} // gls
} // deqp