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

#include "es3fFboRenderTest.hpp"
#include "sglrContextUtil.hpp"
#include "sglrGLContext.hpp"
#include "sglrReferenceContext.hpp"
#include "es3fFboTestUtil.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "deRandom.h"
#include "deString.h"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

#include <sstream>

using std::vector;
using std::string;
using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::RGBA;
using tcu::Surface;

namespace deqp
{
namespace gles3
{
namespace Functional
{

using glw::GLenum;
using namespace FboTestUtil;

class FboConfig
{
public:
	FboConfig (deUint32 buffers_, deUint32 colorType_, deUint32 colorFormat_, deUint32 depthStencilType_, deUint32 depthStencilFormat_, int width_ = 0, int height_ = 0, int samples_ = 0)
		: buffers				(buffers_)
		, colorType				(colorType_)
		, colorFormat			(colorFormat_)
		, depthStencilType		(depthStencilType_)
		, depthStencilFormat	(depthStencilFormat_)
		, width					(width_)
		, height				(height_)
		, samples				(samples_)
	{
	}

	FboConfig (void)
		: buffers				(0)
		, colorType				(GL_NONE)
		, colorFormat			(GL_NONE)
		, depthStencilType		(GL_NONE)
		, depthStencilFormat	(GL_NONE)
		, width					(0)
		, height				(0)
		, samples				(0)
	{
	}

	std::string				getName			(void) const;

	deUint32				buffers;		//!< Buffer bit mask (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|...)

	GLenum					colorType;		//!< GL_TEXTURE_2D, GL_TEXTURE_CUBE_MAP, GL_RENDERBUFFER
	GLenum					colorFormat;	//!< Internal format for color buffer texture or renderbuffer

	GLenum					depthStencilType;
	GLenum					depthStencilFormat;

	int						width;
	int						height;
	int						samples;
};

static const char* getTypeName (GLenum type)
{
	switch (type)
	{
		case GL_TEXTURE_2D:		return "tex2d";
		case GL_RENDERBUFFER:	return "rbo";
		default:
			TCU_FAIL("Unknown type");
	}
}

std::string FboConfig::getName (void) const
{
	std::ostringstream name;

	DE_ASSERT(buffers & GL_COLOR_BUFFER_BIT);
	name << getTypeName(colorType) << "_" << getFormatName(colorFormat);

	if (buffers & GL_DEPTH_BUFFER_BIT)
		name << "_depth";
	if (buffers & GL_STENCIL_BUFFER_BIT)
		name << "_stencil";

	if (buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))
		name << "_" << getTypeName(depthStencilType) << "_" << getFormatName(depthStencilFormat);

	return name.str();
}

class Framebuffer
{
public:
						Framebuffer				(sglr::Context& context, const FboConfig& config, int width, int height, deUint32 fbo = 0, deUint32 colorBuffer = 0, deUint32 depthStencilBuffer = 0);
						~Framebuffer			(void);

	const FboConfig&	getConfig				(void) const { return m_config;				}
	deUint32			getFramebuffer			(void) const { return m_framebuffer;		}
	deUint32			getColorBuffer			(void) const { return m_colorBuffer;		}
	deUint32			getDepthStencilBuffer	(void) const { return m_depthStencilBuffer;	}

	void				checkCompleteness		(void);

private:
	deUint32			createTex2D				(deUint32 name, GLenum format, int width, int height);
	deUint32			createRbo				(deUint32 name, GLenum format, int width, int height);
	void				destroyBuffer			(deUint32 name, GLenum type);

	FboConfig			m_config;
	sglr::Context&		m_context;
	deUint32			m_framebuffer;
	deUint32			m_colorBuffer;
	deUint32			m_depthStencilBuffer;
};

static std::vector<std::string> getEnablingExtensions (deUint32 format)
{
	std::vector<std::string> out;

	switch (format)
	{
		case GL_RGB16F:
			out.push_back("GL_EXT_color_buffer_half_float");
			break;

		case GL_RGBA16F:
		case GL_RG16F:
		case GL_R16F:
			out.push_back("GL_EXT_color_buffer_half_float");

		case GL_RGBA32F:
		case GL_RGB32F:
		case GL_R11F_G11F_B10F:
		case GL_RG32F:
		case GL_R32F:
			out.push_back("GL_EXT_color_buffer_float");

		default:
			break;
	}

	return out;
}

static bool isExtensionSupported (sglr::Context& context, const char* name)
{
	std::istringstream extensions(context.getString(GL_EXTENSIONS));
	std::string extension;

	while (std::getline(extensions, extension, ' '))
	{
		if (extension == name)
			return true;
	}

	return false;
}

static bool isAnyExtensionSupported (sglr::Context& context, const std::vector<std::string>& requiredExts)
{
	if (requiredExts.empty())
		return true;

	for (std::vector<std::string>::const_iterator iter = requiredExts.begin(); iter != requiredExts.end(); iter++)
	{
		const std::string& extension = *iter;

		if (isExtensionSupported(context, extension.c_str()))
			return true;
	}

	return false;
}

template<typename T>
static std::string join (const std::vector<T>& list, const std::string& sep)
{
	std::ostringstream	out;

	for (typename std::vector<T>::const_iterator iter = list.begin(); iter != list.end(); iter++)
	{
		if (iter != list.begin())
			out << sep;
		out << *iter;
	}

	return out.str();
}

static void checkColorFormatSupport (sglr::Context& context, deUint32 sizedFormat)
{
	const std::vector<std::string> requiredExts = getEnablingExtensions(sizedFormat);

	if (!isAnyExtensionSupported(context, requiredExts))
	{
		std::string	errMsg	= "Format not supported, requires "
							+ ((requiredExts.size() == 1) ? requiredExts[0] : " one of the following: " + join(requiredExts, ", "));

		throw tcu::NotSupportedError(errMsg);
	}
}

Framebuffer::Framebuffer (sglr::Context& context, const FboConfig& config, int width, int height, deUint32 fbo, deUint32 colorBufferName, deUint32 depthStencilBufferName)
	: m_config				(config)
	, m_context				(context)
	, m_framebuffer			(fbo)
	, m_colorBuffer			(0)
	, m_depthStencilBuffer	(0)
{
	// Verify that color format is supported
	checkColorFormatSupport(context, config.colorFormat);

	if (m_framebuffer == 0)
		context.genFramebuffers(1, &m_framebuffer);
	context.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

	if (m_config.buffers & (GL_COLOR_BUFFER_BIT))
	{
		switch (m_config.colorType)
		{
			case GL_TEXTURE_2D:
				m_colorBuffer = createTex2D(colorBufferName, m_config.colorFormat, width, height);
				context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer, 0);
				break;

			case GL_RENDERBUFFER:
				m_colorBuffer = createRbo(colorBufferName, m_config.colorFormat, width, height);
				context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorBuffer);
				break;

			default:
				TCU_FAIL("Unsupported type");
		}
	}

	if (m_config.buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT))
	{
		switch (m_config.depthStencilType)
		{
			case GL_TEXTURE_2D:		m_depthStencilBuffer = createTex2D(depthStencilBufferName, m_config.depthStencilFormat, width, height);		break;
			case GL_RENDERBUFFER:	m_depthStencilBuffer = createRbo(depthStencilBufferName, m_config.depthStencilFormat, width, height);		break;
			default:
				TCU_FAIL("Unsupported type");
		}
	}

	for (int ndx = 0; ndx < 2; ndx++)
	{
		deUint32	bit		= ndx ? GL_STENCIL_BUFFER_BIT : GL_DEPTH_BUFFER_BIT;
		deUint32	point	= ndx ? GL_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;

		if ((m_config.buffers & bit) == 0)
			continue; /* Not used. */

		switch (m_config.depthStencilType)
		{
			case GL_TEXTURE_2D:		context.framebufferTexture2D(GL_FRAMEBUFFER, point, GL_TEXTURE_2D, m_depthStencilBuffer, 0);	break;
			case GL_RENDERBUFFER:	context.framebufferRenderbuffer(GL_FRAMEBUFFER, point, GL_RENDERBUFFER, m_depthStencilBuffer);	break;
			default:
				DE_ASSERT(false);
		}
	}

	GLenum err = m_context.getError();
	if (err != GL_NO_ERROR)
		throw glu::Error(err, glu::getErrorStr(err).toString().c_str(), "", __FILE__, __LINE__);

	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
}

Framebuffer::~Framebuffer (void)
{
	m_context.deleteFramebuffers(1, &m_framebuffer);
	destroyBuffer(m_colorBuffer, m_config.colorType);
	destroyBuffer(m_depthStencilBuffer, m_config.depthStencilType);
}

void Framebuffer::checkCompleteness (void)
{
	m_context.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
	GLenum status = m_context.checkFramebufferStatus(GL_FRAMEBUFFER);
	m_context.bindFramebuffer(GL_FRAMEBUFFER, 0);
	if (status != GL_FRAMEBUFFER_COMPLETE)
		throw FboIncompleteException(status, __FILE__, __LINE__);
}

deUint32 Framebuffer::createTex2D (deUint32 name, GLenum format, int width, int height)
{
	if (name == 0)
		m_context.genTextures(1, &name);

	m_context.bindTexture(GL_TEXTURE_2D, name);
	m_context.texImage2D(GL_TEXTURE_2D, 0, format, width, height);

	if (!deIsPowerOfTwo32(width) || !deIsPowerOfTwo32(height))
	{
		// Set wrap mode to clamp for NPOT FBOs
		m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	}

	m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	m_context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	return name;
}

deUint32 Framebuffer::createRbo (deUint32 name, GLenum format, int width, int height)
{
	if (name == 0)
		m_context.genRenderbuffers(1, &name);

	m_context.bindRenderbuffer(GL_RENDERBUFFER, name);
	m_context.renderbufferStorage(GL_RENDERBUFFER, format, width, height);

	return name;
}

void Framebuffer::destroyBuffer (deUint32 name, GLenum type)
{
	if (type == GL_TEXTURE_2D || type == GL_TEXTURE_CUBE_MAP)
		m_context.deleteTextures(1, &name);
	else if (type == GL_RENDERBUFFER)
		m_context.deleteRenderbuffers(1, &name);
	else
		DE_ASSERT(type == GL_NONE);
}

static void createMetaballsTex2D (sglr::Context& context, deUint32 name, GLenum format, GLenum dataType, int width, int height)
{
	tcu::TextureFormat	texFormat	= glu::mapGLTransferFormat(format, dataType);
	tcu::TextureLevel	level		(texFormat, width, height);

	tcu::fillWithMetaballs(level.getAccess(), 5, name ^ width ^ height);

	context.bindTexture(GL_TEXTURE_2D, name);
	context.texImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, level.getAccess().getDataPtr());
	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}

static void createQuadsTex2D (sglr::Context& context, deUint32 name, GLenum format, GLenum dataType, int width, int height)
{
	tcu::TextureFormat	texFormat	= glu::mapGLTransferFormat(format, dataType);
	tcu::TextureLevel	level		(texFormat, width, height);

	tcu::fillWithRGBAQuads(level.getAccess());

	context.bindTexture(GL_TEXTURE_2D, name);
	context.texImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, level.getAccess().getDataPtr());
	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}

class FboRenderCase : public TestCase
{
public:
								FboRenderCase			(Context& context, const char* name, const char* description, const FboConfig& config);
	virtual						~FboRenderCase			(void) {}

	virtual IterateResult		iterate					(void);
	virtual void				render					(sglr::Context& fboContext, Surface& dst) = DE_NULL;

	bool						compare					(const tcu::Surface& reference, const tcu::Surface& result);

protected:
	const FboConfig				m_config;
};

FboRenderCase::FboRenderCase (Context& context, const char* name, const char* description, const FboConfig& config)
	: TestCase	(context, name, description)
	, m_config	(config)
{
}

TestCase::IterateResult FboRenderCase::iterate (void)
{
	tcu::Vec4					clearColor				= tcu::Vec4(0.125f, 0.25f, 0.5f, 1.0f);
	glu::RenderContext&			renderCtx				= m_context.getRenderContext();
	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
	tcu::TestLog&				log						= m_testCtx.getLog();
	const char*					failReason				= DE_NULL;

	// Position & size for context
	deRandom rnd;
	deRandom_init(&rnd, deStringHash(getName()));

	int		width	= deMin32(renderTarget.getWidth(), 128);
	int		height	= deMin32(renderTarget.getHeight(), 128);
	int		xMax	= renderTarget.getWidth()-width+1;
	int		yMax	= renderTarget.getHeight()-height+1;
	int		x		= deRandom_getUint32(&rnd) % xMax;
	int		y		= deRandom_getUint32(&rnd) % yMax;

	tcu::Surface	gles3Frame	(width, height);
	tcu::Surface	refFrame	(width, height);
	GLenum			gles3Error;
	GLenum			refError;

	// Render using GLES3
	try
	{
		sglr::GLContext context(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));

		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

		render(context, gles3Frame); // Call actual render func
		gles3Error = context.getError();
	}
	catch (const FboIncompleteException& e)
	{
		if (e.getReason() == GL_FRAMEBUFFER_UNSUPPORTED)
		{
			// Mark test case as unsupported
			log << e;
			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
			return STOP;
		}
		else
			throw; // Propagate error
	}

	// Render reference image
	{
		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());

		context.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
		context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

		render(context, refFrame);
		refError = context.getError();
	}

	// Compare error codes
	bool errorCodesOk = (gles3Error == refError);

	if (!errorCodesOk)
	{
		log << tcu::TestLog::Message << "Error code mismatch: got " << glu::getErrorStr(gles3Error) << ", expected " << glu::getErrorStr(refError) << tcu::TestLog::EndMessage;
		failReason = "Got unexpected error";
	}

	// Compare images
	bool imagesOk = compare(refFrame, gles3Frame);

	if (!imagesOk && !failReason)
		failReason = "Image comparison failed";

	// Store test result
	bool isOk = errorCodesOk && imagesOk;
	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
							isOk ? "Pass"				: failReason);

	return STOP;
}

bool FboRenderCase::compare (const tcu::Surface& reference, const tcu::Surface& result)
{
	const tcu::RGBA threshold (tcu::max(getFormatThreshold(m_config.colorFormat), tcu::RGBA(12, 12, 12, 12)));

	return tcu::bilinearCompare(m_testCtx.getLog(), "ComparisonResult", "Image comparison result", reference.getAccess(), result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
}

namespace FboCases
{

class StencilClearsTest : public FboRenderCase
{
public:
						StencilClearsTest		(Context& context, const FboConfig& config);
	virtual				~StencilClearsTest		(void) {};

	void				render					(sglr::Context& context, Surface& dst);
};

StencilClearsTest::StencilClearsTest (Context& context, const FboConfig& config)
	: FboRenderCase	(context, config.getName().c_str(), "Stencil clears", config)
{
}

void StencilClearsTest::render (sglr::Context& context, Surface& dst)
{
	tcu::TextureFormat		colorFormat			= glu::mapGLInternalFormat(m_config.colorFormat);
	glu::DataType			fboSamplerType		= glu::getSampler2DType(colorFormat);
	glu::DataType			fboOutputType		= getFragmentOutputType(colorFormat);
	tcu::TextureFormatInfo	fboRangeInfo		= tcu::getTextureFormatInfo(colorFormat);
	Vec4					fboOutScale			= fboRangeInfo.valueMax - fboRangeInfo.valueMin;
	Vec4					fboOutBias			= fboRangeInfo.valueMin;

	Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, fboOutputType);
	Texture2DShader			texFromFboShader	(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);

	deUint32				texToFboShaderID	= context.createProgram(&texToFboShader);
	deUint32				texFromFboShaderID	= context.createProgram(&texFromFboShader);

	deUint32				metaballsTex		= 1;
	deUint32				quadsTex			= 2;
	int						width				= 128;
	int						height				= 128;

	texToFboShader.setOutScaleBias(fboOutScale, fboOutBias);
	texFromFboShader.setTexScaleBias(0, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);

	createQuadsTex2D(context, quadsTex, GL_RGBA, GL_UNSIGNED_BYTE, width, height);
	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, width, height);

	Framebuffer fbo(context, m_config, width, height);
	fbo.checkCompleteness();

	// Bind framebuffer and clear
	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	context.viewport(0, 0, width, height);
	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	// Do stencil clears
	context.enable(GL_SCISSOR_TEST);
	context.scissor(10, 16, 32, 120);
	context.clearStencil(1);
	context.clear(GL_STENCIL_BUFFER_BIT);
	context.scissor(16, 32, 100, 64);
	context.clearStencil(2);
	context.clear(GL_STENCIL_BUFFER_BIT);
	context.disable(GL_SCISSOR_TEST);

	// Draw 2 textures with stecil tests
	context.enable(GL_STENCIL_TEST);

	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	context.stencilFunc(GL_EQUAL, 1, 0xffu);

	texToFboShader.setUniforms(context, texToFboShaderID);
	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));

	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	context.stencilFunc(GL_EQUAL, 2, 0xffu);

	texToFboShader.setUniforms(context, texToFboShaderID);
	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));

	context.disable(GL_STENCIL_TEST);

	if (fbo.getConfig().colorType == GL_TEXTURE_2D)
	{
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
		context.viewport(0, 0, context.getWidth(), context.getHeight());

		texFromFboShader.setUniforms(context, texFromFboShaderID);
		sglr::drawQuad(context, texFromFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		readPixels(context, dst, 0, 0, width, height, colorFormat, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
}

class SharedColorbufferTest : public FboRenderCase
{
public:
						SharedColorbufferTest			(Context& context, const FboConfig& config);
	virtual				~SharedColorbufferTest			(void) {};

	void				render							(sglr::Context& context, Surface& dst);
};

SharedColorbufferTest::SharedColorbufferTest (Context& context, const FboConfig& config)
	: FboRenderCase	(context, config.getName().c_str(), "Shared colorbuffer", config)
{
}

void SharedColorbufferTest::render (sglr::Context& context, Surface& dst)
{
	Texture2DShader			texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
	FlatColorShader			flatShader		(glu::TYPE_FLOAT_VEC4);
	deUint32				texShaderID		= context.createProgram(&texShader);
	deUint32				flatShaderID	= context.createProgram(&flatShader);

	int						width			= 128;
	int						height			= 128;
	deUint32				quadsTex		= 1;
	deUint32				metaballsTex	= 2;
	bool					stencil			= (m_config.buffers & GL_STENCIL_BUFFER_BIT) != 0;

	context.disable(GL_DITHER);

	// Textures
	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, 64, 64);

	context.viewport(0, 0, width, height);

	// Fbo A
	Framebuffer fboA(context, m_config, width, height);
	fboA.checkCompleteness();

	// Fbo B - don't create colorbuffer
	FboConfig cfg = m_config;
	cfg.buffers		&= ~GL_COLOR_BUFFER_BIT;
	cfg.colorType	 = GL_NONE;
	cfg.colorFormat	 = GL_NONE;
	Framebuffer fboB(context, cfg, width, height);

	// Attach color buffer from fbo A
	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
	switch (m_config.colorType)
	{
		case GL_TEXTURE_2D:
			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboA.getColorBuffer(), 0);
			break;

		case GL_RENDERBUFFER:
			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fboA.getColorBuffer());
			break;

		default:
			DE_ASSERT(DE_FALSE);
	}

	// Clear depth and stencil in fbo B
	context.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	// Render quads to fbo 1, with depth 0.0
	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	if (stencil)
	{
		// Stencil to 1 in fbo A
		context.clearStencil(1);
		context.clear(GL_STENCIL_BUFFER_BIT);
	}

	texShader.setUniforms(context, texShaderID);

	context.enable(GL_DEPTH_TEST);
	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
	context.disable(GL_DEPTH_TEST);

	// Blend metaballs to fbo 2
	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	context.enable(GL_BLEND);
	context.blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	// Render small quad that is only visible if depth buffer is not shared with fbo A - or there is no depth bits
	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	context.enable(GL_DEPTH_TEST);
	sglr::drawQuad(context, texShaderID, Vec3(0.5f, 0.5f, 0.5f), Vec3(1.0f, 1.0f, 0.5f));
	context.disable(GL_DEPTH_TEST);

	if (stencil)
	{
		flatShader.setColor(context, flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));

		// Clear subset of stencil buffer to 1
		context.enable(GL_SCISSOR_TEST);
		context.scissor(10, 10, 12, 25);
		context.clearStencil(1);
		context.clear(GL_STENCIL_BUFFER_BIT);
		context.disable(GL_SCISSOR_TEST);

		// Render quad with stencil mask == 1
		context.enable(GL_STENCIL_TEST);
		context.stencilFunc(GL_EQUAL, 1, 0xffu);
		sglr::drawQuad(context, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		context.disable(GL_STENCIL_TEST);
	}

	// Get results
	if (fboA.getConfig().colorType == GL_TEXTURE_2D)
	{
		texShader.setUniforms(context, texShaderID);

		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.bindTexture(GL_TEXTURE_2D, fboA.getColorBuffer());
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		readPixels(context, dst, 0, 0, width, height, glu::mapGLInternalFormat(fboA.getConfig().colorFormat), Vec4(1.0f), Vec4(0.0f));
}

class SharedColorbufferClearsTest : public FboRenderCase
{
public:
					SharedColorbufferClearsTest		(Context& context, const FboConfig& config);
	virtual			~SharedColorbufferClearsTest	(void) {}

	void			render							(sglr::Context& context, Surface& dst);
};

SharedColorbufferClearsTest::SharedColorbufferClearsTest (Context& context, const FboConfig& config)
	: FboRenderCase	(context, config.getName().c_str(), "Shared colorbuffer clears", config)
{
}

void SharedColorbufferClearsTest::render (sglr::Context& context, Surface& dst)
{
	tcu::TextureFormat		colorFormat		= glu::mapGLInternalFormat(m_config.colorFormat);
	glu::DataType			fboSamplerType	= glu::getSampler2DType(colorFormat);
	int						width			= 128;
	int						height			= 128;
	deUint32				colorbuffer		= 1;

	// Check for format support.
	checkColorFormatSupport(context, m_config.colorFormat);

	// Single colorbuffer
	if (m_config.colorType == GL_TEXTURE_2D)
	{
		context.bindTexture(GL_TEXTURE_2D, colorbuffer);
		context.texImage2D(GL_TEXTURE_2D, 0, m_config.colorFormat, width, height);
		context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}
	else
	{
		DE_ASSERT(m_config.colorType == GL_RENDERBUFFER);
		context.bindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
		context.renderbufferStorage(GL_RENDERBUFFER, m_config.colorFormat, width, height);
	}

	// Multiple framebuffers sharing the colorbuffer
	for (int fbo = 1; fbo <= 3; fbo++)
	{
		context.bindFramebuffer(GL_FRAMEBUFFER, fbo);

		if (m_config.colorType == GL_TEXTURE_2D)
			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
		else
			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer);
	}

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	// Check completeness
	{
		GLenum status = context.checkFramebufferStatus(GL_FRAMEBUFFER);
		if (status != GL_FRAMEBUFFER_COMPLETE)
			throw FboIncompleteException(status, __FILE__, __LINE__);
	}

	// Render to them
	context.viewport(0, 0, width, height);
	context.clearColor(0.0f, 0.0f, 1.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT);

	context.enable(GL_SCISSOR_TEST);

	context.bindFramebuffer(GL_FRAMEBUFFER, 2);
	context.clearColor(0.6f, 0.0f, 0.0f, 1.0f);
	context.scissor(10, 10, 64, 64);
	context.clear(GL_COLOR_BUFFER_BIT);
	context.clearColor(0.0f, 0.6f, 0.0f, 1.0f);
	context.scissor(60, 60, 40, 20);
	context.clear(GL_COLOR_BUFFER_BIT);

	context.bindFramebuffer(GL_FRAMEBUFFER, 3);
	context.clearColor(0.0f, 0.0f, 0.6f, 1.0f);
	context.scissor(20, 20, 100, 10);
	context.clear(GL_COLOR_BUFFER_BIT);

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
	context.clearColor(0.6f, 0.0f, 0.6f, 1.0f);
	context.scissor(20, 20, 5, 100);
	context.clear(GL_COLOR_BUFFER_BIT);

	context.disable(GL_SCISSOR_TEST);

	if (m_config.colorType == GL_TEXTURE_2D)
	{
		Texture2DShader shader(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);
		deUint32 shaderID = context.createProgram(&shader);

		shader.setUniforms(context, shaderID);

		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		sglr::drawQuad(context, shaderID, Vec3(-0.9f, -0.9f, 0.0f), Vec3(0.9f, 0.9f, 0.0f));
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		readPixels(context, dst, 0, 0, width, height, colorFormat, Vec4(1.0f), Vec4(0.0f));
}

class SharedDepthStencilTest : public FboRenderCase
{
public:
					SharedDepthStencilTest		(Context& context, const FboConfig& config);
	virtual			~SharedDepthStencilTest		(void) {};

	static bool		isConfigSupported			(const FboConfig& config);
	void			render						(sglr::Context& context, Surface& dst);
};

SharedDepthStencilTest::SharedDepthStencilTest (Context& context, const FboConfig& config)
	: FboRenderCase	(context, config.getName().c_str(), "Shared depth/stencilbuffer", config)
{
}

bool SharedDepthStencilTest::isConfigSupported (const FboConfig& config)
{
	return (config.buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) != 0;
}

void SharedDepthStencilTest::render (sglr::Context& context, Surface& dst)
{
	Texture2DShader	texShader		(DataTypes() << glu::TYPE_SAMPLER_2D, glu::TYPE_FLOAT_VEC4);
	FlatColorShader	flatShader		(glu::TYPE_FLOAT_VEC4);
	deUint32		texShaderID		= context.createProgram(&texShader);
	deUint32		flatShaderID	= context.createProgram(&flatShader);
	int				width			= 128;
	int				height			= 128;
//	bool			depth			= (m_config.buffers & GL_DEPTH_BUFFER_BIT) != 0;
	bool			stencil			= (m_config.buffers & GL_STENCIL_BUFFER_BIT) != 0;

	// Textures
	deUint32 metaballsTex	= 5;
	deUint32 quadsTex		= 6;
	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);

	context.viewport(0, 0, width, height);

	// Fbo A
	Framebuffer fboA(context, m_config, width, height);
	fboA.checkCompleteness();

	// Fbo B
	FboConfig cfg = m_config;
	cfg.buffers				&= ~(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
	cfg.depthStencilType	 = GL_NONE;
	cfg.depthStencilFormat	 = GL_NONE;
	Framebuffer fboB(context, cfg, width, height);

	// Bind depth/stencil buffers from fbo A to fbo B
	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
	for (int ndx = 0; ndx < 2; ndx++)
	{
		deUint32	bit		= ndx ? GL_STENCIL_BUFFER_BIT : GL_DEPTH_BUFFER_BIT;
		deUint32	point	= ndx ? GL_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;

		if ((m_config.buffers & bit) == 0)
			continue;

		switch (m_config.depthStencilType)
		{
			case GL_TEXTURE_2D:		context.framebufferTexture2D(GL_FRAMEBUFFER, point, GL_TEXTURE_2D, fboA.getDepthStencilBuffer(), 0);	break;
			case GL_RENDERBUFFER:	context.framebufferRenderbuffer(GL_FRAMEBUFFER, point, GL_RENDERBUFFER, fboA.getDepthStencilBuffer());	break;
			default:
				TCU_FAIL("Not implemented");
		}
	}

	// Setup uniforms
	texShader.setUniforms(context, texShaderID);

	// Clear color to red and stencil to 1 in fbo B.
	context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
	context.clearStencil(1);
	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	context.enable(GL_DEPTH_TEST);

	// Render quad to fbo A
	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	if (stencil)
	{
		// Clear subset of stencil buffer to 0 in fbo A
		context.enable(GL_SCISSOR_TEST);
		context.scissor(10, 10, 12, 25);
		context.clearStencil(0);
		context.clear(GL_STENCIL_BUFFER_BIT);
		context.disable(GL_SCISSOR_TEST);
	}

	// Render metaballs to fbo B
	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));

	context.disable(GL_DEPTH_TEST);

	if (stencil)
	{
		// Render quad with stencil mask == 0
		context.enable(GL_STENCIL_TEST);
		context.stencilFunc(GL_EQUAL, 0, 0xffu);
		context.useProgram(flatShaderID);
		flatShader.setColor(context, flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
		sglr::drawQuad(context, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
		context.disable(GL_STENCIL_TEST);
	}

	if (m_config.colorType == GL_TEXTURE_2D)
	{
		// Render both to screen
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		context.bindTexture(GL_TEXTURE_2D, fboA.getColorBuffer());
		sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f));
		context.bindTexture(GL_TEXTURE_2D, fboB.getColorBuffer());
		sglr::drawQuad(context, texShaderID, Vec3(0.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
	{
		// Read results from fbo B
		readPixels(context, dst, 0, 0, width, height, glu::mapGLInternalFormat(m_config.colorFormat), Vec4(1.0f), Vec4(0.0f));
	}
}

#if 0
class TexSubImageAfterRenderTest : public FboRenderCase
{
public:
					TexSubImageAfterRenderTest		(Context& context, const FboConfig& config);
	virtual			~TexSubImageAfterRenderTest		(void) {}

	void			render							(sglr::Context& context, Surface& dst);
};

TexSubImageAfterRenderTest::TexSubImageAfterRenderTest (Context& context, const FboConfig& config)
	: FboRenderCase(context, (string("after_render_") + config.getName()).c_str(), "TexSubImage after rendering to texture", config)
{
}

void TexSubImageAfterRenderTest::render (sglr::Context& context, Surface& dst)
{
	using sglr::TexturedQuadOp;

	bool isRGBA = true;

	Surface fourQuads(Surface::PIXELFORMAT_RGB, 64, 64);
	tcu::SurfaceUtil::fillWithFourQuads(fourQuads);

	Surface metaballs(isRGBA ? Surface::PIXELFORMAT_RGBA : Surface::PIXELFORMAT_RGB, 64, 64);
	tcu::SurfaceUtil::fillWithMetaballs(metaballs, 5, 3);

	deUint32 fourQuadsTex = 1;
	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, fourQuads);

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 fboTex = 2;
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	context.texImage2D(GL_TEXTURE_2D, 0, isRGBA ? GL_RGBA : GL_RGB, 128, 128);
	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTex, 0);

	// Render to fbo
	context.viewport(0, 0, 128, 128);
	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));

	// Update texture using TexSubImage2D
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, metaballs);

	// Draw to screen
	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
	context.viewport(0, 0, context.getWidth(), context.getHeight());
	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));
	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
}

class TexSubImageBetweenRenderTest : public FboRenderCase
{
public:
					TexSubImageBetweenRenderTest		(Context& context, const FboConfig& config);
	virtual			~TexSubImageBetweenRenderTest		(void) {}

	void			render								(sglr::Context& context, Surface& dst);
};

TexSubImageBetweenRenderTest::TexSubImageBetweenRenderTest (Context& context, const FboConfig& config)
	: FboRenderCase(context, (string("between_render_") + config.getName()).c_str(), "TexSubImage between rendering calls", config)
{
}

void TexSubImageBetweenRenderTest::render (sglr::Context& context, Surface& dst)
{
	using sglr::TexturedQuadOp;
	using sglr::BlendTextureOp;

	bool isRGBA = true;

	Surface fourQuads(Surface::PIXELFORMAT_RGB, 64, 64);
	tcu::SurfaceUtil::fillWithFourQuads(fourQuads);

	Surface metaballs(isRGBA ? Surface::PIXELFORMAT_RGBA : Surface::PIXELFORMAT_RGB, 64, 64);
	tcu::SurfaceUtil::fillWithMetaballs(metaballs, 5, 3);

	Surface metaballs2(Surface::PIXELFORMAT_RGBA, 64, 64);
	tcu::SurfaceUtil::fillWithMetaballs(metaballs2, 5, 4);

	deUint32 metaballsTex = 3;
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, metaballs2);

	deUint32 fourQuadsTex = 1;
	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, fourQuads);

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 fboTex = 2;
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	context.texImage2D(GL_TEXTURE_2D, 0, isRGBA ? GL_RGBA : GL_RGB, 128, 128);
	context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTex, 0);

	// Render to fbo
	context.viewport(0, 0, 128, 128);
	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));

	// Update texture using TexSubImage2D
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, metaballs);

	// Render again to fbo
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	context.draw(BlendTextureOp(0));

	// Draw to screen
	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
	context.viewport(0, 0, context.getWidth(), context.getHeight());
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	context.draw(TexturedQuadOp(-1.0f, -1.0f, 1.0f, 1.0f, 0));

	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
}
#endif

class ResizeTest : public FboRenderCase
{
public:
					ResizeTest				(Context& context, const FboConfig& config);
	virtual			~ResizeTest				(void) {}

	void			render					(sglr::Context& context, Surface& dst);
};

ResizeTest::ResizeTest (Context& context, const FboConfig& config)
	: FboRenderCase	(context, config.getName().c_str(), "Resize framebuffer", config)
{
}

void ResizeTest::render (sglr::Context& context, Surface& dst)
{
	tcu::TextureFormat		colorFormat			= glu::mapGLInternalFormat(m_config.colorFormat);
	glu::DataType			fboSamplerType		= glu::getSampler2DType(colorFormat);
	glu::DataType			fboOutputType		= getFragmentOutputType(colorFormat);
	tcu::TextureFormatInfo	fboRangeInfo		= tcu::getTextureFormatInfo(colorFormat);
	Vec4					fboOutScale			= fboRangeInfo.valueMax - fboRangeInfo.valueMin;
	Vec4					fboOutBias			= fboRangeInfo.valueMin;

	Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, fboOutputType);
	Texture2DShader			texFromFboShader	(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);
	FlatColorShader			flatShader			(fboOutputType);
	deUint32				texToFboShaderID	= context.createProgram(&texToFboShader);
	deUint32				texFromFboShaderID	= context.createProgram(&texFromFboShader);
	deUint32				flatShaderID		= context.createProgram(&flatShader);

	deUint32				quadsTex			= 1;
	deUint32				metaballsTex		= 2;
	bool					depth				= (m_config.buffers & GL_DEPTH_BUFFER_BIT)		!= 0;
	bool					stencil				= (m_config.buffers & GL_STENCIL_BUFFER_BIT)	!= 0;
	int						initialWidth		= 128;
	int						initialHeight		= 128;
	int						newWidth			= 64;
	int						newHeight			= 32;

	texToFboShader.setOutScaleBias(fboOutScale, fboOutBias);
	texFromFboShader.setTexScaleBias(0, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);

	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 32, 32);

	Framebuffer fbo(context, m_config, initialWidth, initialHeight);
	fbo.checkCompleteness();

	// Setup shaders
	texToFboShader.setUniforms	(context, texToFboShaderID);
	texFromFboShader.setUniforms(context, texFromFboShaderID);
	flatShader.setColor			(context, flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f) * fboOutScale + fboOutBias);

	// Render quads
	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	context.viewport(0, 0, initialWidth, initialHeight);
	clearColorBuffer(context, colorFormat, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
	context.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	if (fbo.getConfig().colorType == GL_TEXTURE_2D)
	{
		// Render fbo to screen
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
		sglr::drawQuad(context, texFromFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

		// Restore binding
		context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	}

	// Resize buffers
	switch (fbo.getConfig().colorType)
	{
		case GL_TEXTURE_2D:
			context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
			context.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().colorFormat, newWidth, newHeight);
			break;

		case GL_RENDERBUFFER:
			context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getColorBuffer());
			context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().colorFormat, newWidth, newHeight);
			break;

		default:
			DE_ASSERT(DE_FALSE);
	}

	if (depth || stencil)
	{
		switch (fbo.getConfig().depthStencilType)
		{
			case GL_TEXTURE_2D:
				context.bindTexture(GL_TEXTURE_2D, fbo.getDepthStencilBuffer());
				context.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().depthStencilFormat, newWidth, newHeight);
				break;

			case GL_RENDERBUFFER:
				context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getDepthStencilBuffer());
				context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().depthStencilFormat, newWidth, newHeight);
				break;

			default:
				DE_ASSERT(false);
		}
	}

	// Render to resized fbo
	context.viewport(0, 0, newWidth, newHeight);
	clearColorBuffer(context, colorFormat, tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
	context.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	context.enable(GL_DEPTH_TEST);

	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	sglr::drawQuad(context, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));

	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	sglr::drawQuad(context, texToFboShaderID, Vec3(0.0f, 0.0f, -1.0f), Vec3(+1.0f, +1.0f, 1.0f));

	context.disable(GL_DEPTH_TEST);

	if (stencil)
	{
		context.enable(GL_SCISSOR_TEST);
		context.clearStencil(1);
		context.scissor(10, 10, 5, 15);
		context.clear(GL_STENCIL_BUFFER_BIT);
		context.disable(GL_SCISSOR_TEST);

		context.enable(GL_STENCIL_TEST);
		context.stencilFunc(GL_EQUAL, 1, 0xffu);
		sglr::drawQuad(context, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));
		context.disable(GL_STENCIL_TEST);
	}

	if (m_config.colorType == GL_TEXTURE_2D)
	{
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		context.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
		sglr::drawQuad(context, texFromFboShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(0.5f, 0.5f, 0.0f));
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		readPixels(context, dst, 0, 0, newWidth, newHeight, colorFormat, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
}

class RecreateBuffersTest : public FboRenderCase
{
public:
					RecreateBuffersTest			(Context& context, const FboConfig& config, deUint32 buffers, bool rebind);
	virtual			~RecreateBuffersTest		(void) {}

	void			render						(sglr::Context& context, Surface& dst);

private:
	deUint32		m_buffers;
	bool			m_rebind;
};

RecreateBuffersTest::RecreateBuffersTest (Context& context, const FboConfig& config, deUint32 buffers, bool rebind)
	: FboRenderCase		(context, (string(config.getName()) + (rebind ? "" : "_no_rebind")).c_str(), "Recreate buffers", config)
	, m_buffers			(buffers)
	, m_rebind			(rebind)
{
}

void RecreateBuffersTest::render (sglr::Context& ctx, Surface& dst)
{
	tcu::TextureFormat		colorFormat			= glu::mapGLInternalFormat(m_config.colorFormat);
	glu::DataType			fboSamplerType		= glu::getSampler2DType(colorFormat);
	glu::DataType			fboOutputType		= getFragmentOutputType(colorFormat);
	tcu::TextureFormatInfo	fboRangeInfo		= tcu::getTextureFormatInfo(colorFormat);
	Vec4					fboOutScale			= fboRangeInfo.valueMax - fboRangeInfo.valueMin;
	Vec4					fboOutBias			= fboRangeInfo.valueMin;

	Texture2DShader			texToFboShader		(DataTypes() << glu::TYPE_SAMPLER_2D, fboOutputType);
	Texture2DShader			texFromFboShader	(DataTypes() << fboSamplerType, glu::TYPE_FLOAT_VEC4);
	FlatColorShader			flatShader			(fboOutputType);
	deUint32				texToFboShaderID	= ctx.createProgram(&texToFboShader);
	deUint32				texFromFboShaderID	= ctx.createProgram(&texFromFboShader);
	deUint32				flatShaderID		= ctx.createProgram(&flatShader);

	int						width				= 128;
	int						height				= 128;
	deUint32				metaballsTex		= 1;
	deUint32				quadsTex			= 2;
	bool					stencil				= (m_config.buffers & GL_STENCIL_BUFFER_BIT) != 0;

	createQuadsTex2D(ctx, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
	createMetaballsTex2D(ctx, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);

	Framebuffer fbo(ctx, m_config, width, height);
	fbo.checkCompleteness();

	// Setup shaders
	texToFboShader.setOutScaleBias(fboOutScale, fboOutBias);
	texFromFboShader.setTexScaleBias(0, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
	texToFboShader.setUniforms	(ctx, texToFboShaderID);
	texFromFboShader.setUniforms(ctx, texFromFboShaderID);
	flatShader.setColor			(ctx, flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f) * fboOutScale + fboOutBias);

	// Draw scene
	ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	ctx.viewport(0, 0, width, height);
	clearColorBuffer(ctx, colorFormat, tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
	ctx.clear(GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	ctx.enable(GL_DEPTH_TEST);

	ctx.bindTexture(GL_TEXTURE_2D, quadsTex);
	sglr::drawQuad(ctx, texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	ctx.disable(GL_DEPTH_TEST);

	if (stencil)
	{
		ctx.enable(GL_SCISSOR_TEST);
		ctx.scissor(width/4, height/4, width/2, height/2);
		ctx.clearStencil(1);
		ctx.clear(GL_STENCIL_BUFFER_BIT);
		ctx.disable(GL_SCISSOR_TEST);
	}

	// Recreate buffers
	if (!m_rebind)
		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);

	DE_ASSERT((m_buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) == 0 ||
			  (m_buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) == (m_config.buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)));

	// Recreate.
	for (int ndx = 0; ndx < 2; ndx++)
	{
		deUint32	bit		= ndx == 0 ? GL_COLOR_BUFFER_BIT
									   : (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
		deUint32	type	= ndx == 0 ? fbo.getConfig().colorType
									   : fbo.getConfig().depthStencilType;
		deUint32	format	= ndx == 0 ? fbo.getConfig().colorFormat
									   : fbo.getConfig().depthStencilFormat;
		deUint32	buf		= ndx == 0 ? fbo.getColorBuffer()
									   : fbo.getDepthStencilBuffer();

		if ((m_buffers & bit) == 0)
			continue;

		switch (type)
		{
			case GL_TEXTURE_2D:
				ctx.deleteTextures(1, &buf);
				ctx.bindTexture(GL_TEXTURE_2D, buf);
				ctx.texImage2D(GL_TEXTURE_2D, 0, format, width, height);
				ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				break;

			case GL_RENDERBUFFER:
				ctx.deleteRenderbuffers(1, &buf);
				ctx.bindRenderbuffer(GL_RENDERBUFFER, buf);
				ctx.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
				break;

			default:
				DE_ASSERT(false);
		}
	}

	// Rebind.
	if (m_rebind)
	{
		for (int ndx = 0; ndx < 3; ndx++)
		{
			deUint32	bit		= ndx == 0 ? GL_COLOR_BUFFER_BIT	:
								  ndx == 1 ? GL_DEPTH_BUFFER_BIT	:
								  ndx == 2 ? GL_STENCIL_BUFFER_BIT	: 0;
			deUint32	point	= ndx == 0 ? GL_COLOR_ATTACHMENT0	:
								  ndx == 1 ? GL_DEPTH_ATTACHMENT	:
								  ndx == 2 ? GL_STENCIL_ATTACHMENT	: 0;
			deUint32	type	= ndx == 0 ? fbo.getConfig().colorType
										   : fbo.getConfig().depthStencilType;
			deUint32	buf		= ndx == 0 ? fbo.getColorBuffer()
										   : fbo.getDepthStencilBuffer();

			if ((m_buffers & bit) == 0)
				continue;

			switch (type)
			{
				case GL_TEXTURE_2D:
					ctx.framebufferTexture2D(GL_FRAMEBUFFER, point, GL_TEXTURE_2D, buf, 0);
					break;

				case GL_RENDERBUFFER:
					ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, point, GL_RENDERBUFFER, buf);
					break;

				default:
					DE_ASSERT(false);
			}
		}
	}

	if (!m_rebind)
		ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());

	ctx.clearStencil(0);
	ctx.clear(m_buffers & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)); // \note Clear only buffers that were re-created
	if (m_buffers & GL_COLOR_BUFFER_BIT)
	{
		// Clearing of integer buffers is undefined so do clearing by rendering flat color.
		sglr::drawQuad(ctx, flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
	}

	ctx.enable(GL_DEPTH_TEST);

	if (stencil)
	{
		// \note Stencil test enabled only if we have stencil buffer
		ctx.enable(GL_STENCIL_TEST);
		ctx.stencilFunc(GL_EQUAL, 0, 0xffu);
	}
	ctx.bindTexture(GL_TEXTURE_2D, metaballsTex);
	sglr::drawQuad(ctx, texToFboShaderID, Vec3(-1.0f, -1.0f, 1.0f), Vec3(1.0f, 1.0f, -1.0f));
	if (stencil)
		ctx.disable(GL_STENCIL_TEST);

	ctx.disable(GL_DEPTH_TEST);

	if (fbo.getConfig().colorType == GL_TEXTURE_2D)
	{
		// Unbind fbo
		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);

		// Draw to screen
		ctx.bindTexture(GL_TEXTURE_2D, fbo.getColorBuffer());
		ctx.viewport(0, 0, ctx.getWidth(), ctx.getHeight());
		sglr::drawQuad(ctx, texFromFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

		// Read from screen
		ctx.readPixels(dst, 0, 0, ctx.getWidth(), ctx.getHeight());
	}
	else
	{
		// Read from fbo
		readPixels(ctx, dst, 0, 0, width, height, colorFormat, fboRangeInfo.lookupScale, fboRangeInfo.lookupBias);
	}
}

} // FboCases

FboRenderTestGroup::FboRenderTestGroup (Context& context)
	: TestCaseGroup(context, "render", "Rendering Tests")
{
}

FboRenderTestGroup::~FboRenderTestGroup (void)
{
}

void FboRenderTestGroup::init (void)
{
	static const deUint32 objectTypes[] =
	{
		GL_TEXTURE_2D,
		GL_RENDERBUFFER
	};

	enum FormatType
	{
		FORMATTYPE_FLOAT = 0,
		FORMATTYPE_FIXED,
		FORMATTYPE_INT,
		FORMATTYPE_UINT,

		FORMATTYPE_LAST
	};

	// Required by specification.
	static const struct
	{
		deUint32	format;
		FormatType	type;
	} colorFormats[] =
	{
		{ GL_RGBA32F,			FORMATTYPE_FLOAT	},
		{ GL_RGBA32I,			FORMATTYPE_INT		},
		{ GL_RGBA32UI,			FORMATTYPE_UINT		},
		{ GL_RGBA16F,			FORMATTYPE_FLOAT	},
		{ GL_RGBA16I,			FORMATTYPE_INT		},
		{ GL_RGBA16UI,			FORMATTYPE_UINT		},
		{ GL_RGB16F,			FORMATTYPE_FLOAT	},
		{ GL_RGBA8,				FORMATTYPE_FIXED	},
		{ GL_RGBA8I,			FORMATTYPE_INT		},
		{ GL_RGBA8UI,			FORMATTYPE_UINT		},
		{ GL_SRGB8_ALPHA8,		FORMATTYPE_FIXED	},
		{ GL_RGB10_A2,			FORMATTYPE_FIXED	},
		{ GL_RGB10_A2UI,		FORMATTYPE_UINT		},
		{ GL_RGBA4,				FORMATTYPE_FIXED	},
		{ GL_RGB5_A1,			FORMATTYPE_FIXED	},
		{ GL_RGB8,				FORMATTYPE_FIXED	},
		{ GL_RGB565,			FORMATTYPE_FIXED	},
		{ GL_R11F_G11F_B10F,	FORMATTYPE_FLOAT	},
		{ GL_RG32F,				FORMATTYPE_FLOAT	},
		{ GL_RG32I,				FORMATTYPE_INT		},
		{ GL_RG32UI,			FORMATTYPE_UINT		},
		{ GL_RG16F,				FORMATTYPE_FLOAT	},
		{ GL_RG16I,				FORMATTYPE_INT		},
		{ GL_RG16UI,			FORMATTYPE_UINT		},
		{ GL_RG8,				FORMATTYPE_FLOAT	},
		{ GL_RG8I,				FORMATTYPE_INT		},
		{ GL_RG8UI,				FORMATTYPE_UINT		},
		{ GL_R32F,				FORMATTYPE_FLOAT	},
		{ GL_R32I,				FORMATTYPE_INT		},
		{ GL_R32UI,				FORMATTYPE_UINT		},
		{ GL_R16F,				FORMATTYPE_FLOAT	},
		{ GL_R16I,				FORMATTYPE_INT		},
		{ GL_R16UI,				FORMATTYPE_UINT		},
		{ GL_R8,				FORMATTYPE_FLOAT	},
		{ GL_R8I,				FORMATTYPE_INT		},
		{ GL_R8UI,				FORMATTYPE_UINT		}
	};

	static const struct
	{
		deUint32	format;
		bool		depth;
		bool		stencil;
	} depthStencilFormats[] =
	{
		{ GL_DEPTH_COMPONENT32F,	true,	false	},
		{ GL_DEPTH_COMPONENT24,		true,	false	},
		{ GL_DEPTH_COMPONENT16,		true,	false	},
		{ GL_DEPTH32F_STENCIL8,		true,	true	},
		{ GL_DEPTH24_STENCIL8,		true,	true	},
		{ GL_STENCIL_INDEX8,		false,	true	}
	};

	using namespace FboCases;

	// .stencil_clear
	tcu::TestCaseGroup* stencilClearGroup = new tcu::TestCaseGroup(m_testCtx, "stencil_clear", "Stencil buffer clears");
	addChild(stencilClearGroup);
	for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
	{
		deUint32	colorType	= GL_TEXTURE_2D;
		deUint32	stencilType	= GL_RENDERBUFFER;
		deUint32	colorFmt	= GL_RGBA8;

		if (!depthStencilFormats[fmtNdx].stencil)
			continue;

		FboConfig config(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT, colorType, colorFmt, stencilType, depthStencilFormats[fmtNdx].format);
		stencilClearGroup->addChild(new StencilClearsTest(m_context, config));
	}

	// .shared_colorbuffer_clear
	tcu::TestCaseGroup* sharedColorbufferClearGroup = new tcu::TestCaseGroup(m_testCtx, "shared_colorbuffer_clear", "Shader colorbuffer clears");
	addChild(sharedColorbufferClearGroup);
	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
	{
		// Clearing of integer buffers is undefined.
		if (colorFormats[colorFmtNdx].type == FORMATTYPE_INT || colorFormats[colorFmtNdx].type == FORMATTYPE_UINT)
			continue;

		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
		{
			FboConfig config(GL_COLOR_BUFFER_BIT, objectTypes[typeNdx], colorFormats[colorFmtNdx].format, GL_NONE, GL_NONE);
			sharedColorbufferClearGroup->addChild(new SharedColorbufferClearsTest(m_context, config));
		}
	}

	// .shared_colorbuffer
	tcu::TestCaseGroup* sharedColorbufferGroup = new tcu::TestCaseGroup(m_testCtx, "shared_colorbuffer", "Shared colorbuffer tests");
	addChild(sharedColorbufferGroup);
	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
	{
		deUint32	depthStencilType	= GL_RENDERBUFFER;
		deUint32	depthStencilFormat	= GL_DEPTH24_STENCIL8;

		// Blending with integer buffers and fp32 targets is not supported.
		if (colorFormats[colorFmtNdx].type == FORMATTYPE_INT	||
			colorFormats[colorFmtNdx].type == FORMATTYPE_UINT	||
			colorFormats[colorFmtNdx].format == GL_RGBA32F		||
			colorFormats[colorFmtNdx].format == GL_RGB32F		||
			colorFormats[colorFmtNdx].format == GL_RG32F		||
			colorFormats[colorFmtNdx].format == GL_R32F)
			continue;

		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
		{
			FboConfig colorOnlyConfig			(GL_COLOR_BUFFER_BIT,											objectTypes[typeNdx], colorFormats[colorFmtNdx].format, GL_NONE, GL_NONE);
			FboConfig colorDepthConfig			(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT,						objectTypes[typeNdx], colorFormats[colorFmtNdx].format, depthStencilType, depthStencilFormat);
			FboConfig colorDepthStencilConfig	(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT,	objectTypes[typeNdx], colorFormats[colorFmtNdx].format, depthStencilType, depthStencilFormat);

			sharedColorbufferGroup->addChild(new SharedColorbufferTest(m_context, colorOnlyConfig));
			sharedColorbufferGroup->addChild(new SharedColorbufferTest(m_context, colorDepthConfig));
			sharedColorbufferGroup->addChild(new SharedColorbufferTest(m_context, colorDepthStencilConfig));
		}
	}

	// .shared_depth_stencil
	tcu::TestCaseGroup* sharedDepthStencilGroup = new tcu::TestCaseGroup(m_testCtx, "shared_depth_stencil", "Shared depth and stencil buffers");
	addChild(sharedDepthStencilGroup);
	for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
	{
		deUint32	colorType		= GL_TEXTURE_2D;
		deUint32	colorFmt		= GL_RGBA8;
		bool		depth			= depthStencilFormats[fmtNdx].depth;
		bool		stencil			= depthStencilFormats[fmtNdx].stencil;

		if (!depth)
			continue; // Not verified.

		// Depth and stencil: both rbo and textures
		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
		{
			FboConfig config(GL_COLOR_BUFFER_BIT|(depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0), colorType, colorFmt, objectTypes[typeNdx], depthStencilFormats[fmtNdx].format);
			sharedDepthStencilGroup->addChild(new SharedDepthStencilTest(m_context, config));
		}
	}

	// .resize
	tcu::TestCaseGroup* resizeGroup = new tcu::TestCaseGroup(m_testCtx, "resize", "FBO resize tests");
	addChild(resizeGroup);
	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
	{
		deUint32 colorFormat = colorFormats[colorFmtNdx].format;

		// Color-only.
		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
		{
			FboConfig config(GL_COLOR_BUFFER_BIT, objectTypes[typeNdx], colorFormat, GL_NONE, GL_NONE);
			resizeGroup->addChild(new ResizeTest(m_context, config));
		}

		// For selected color formats tests depth & stencil variants.
		if (colorFormat == GL_RGBA8 || colorFormat == GL_RGBA16F)
		{
			for (int depthStencilFmtNdx = 0; depthStencilFmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); depthStencilFmtNdx++)
			{
				deUint32	colorType		= GL_TEXTURE_2D;
				bool		depth			= depthStencilFormats[depthStencilFmtNdx].depth;
				bool		stencil			= depthStencilFormats[depthStencilFmtNdx].stencil;

				// Depth and stencil: both rbo and textures
				for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
				{
					if (!depth && objectTypes[typeNdx] != GL_RENDERBUFFER)
						continue; // Not supported.

					FboConfig config(GL_COLOR_BUFFER_BIT|(depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0),
									 colorType, colorFormat,
									 objectTypes[typeNdx], depthStencilFormats[depthStencilFmtNdx].format);
					resizeGroup->addChild(new ResizeTest(m_context, config));
				}
			}
		}
	}

	// .recreate_color
	tcu::TestCaseGroup* recreateColorGroup = new tcu::TestCaseGroup(m_testCtx, "recreate_color", "Recreate colorbuffer tests");
	addChild(recreateColorGroup);
	for (int colorFmtNdx = 0; colorFmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); colorFmtNdx++)
	{
		deUint32	colorFormat			= colorFormats[colorFmtNdx].format;
		deUint32	depthStencilFormat	= GL_DEPTH24_STENCIL8;
		deUint32	depthStencilType	= GL_RENDERBUFFER;

		// Color-only.
		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
		{
			FboConfig config(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT, objectTypes[typeNdx], colorFormat, depthStencilType, depthStencilFormat);
			recreateColorGroup->addChild(new RecreateBuffersTest(m_context, config, GL_COLOR_BUFFER_BIT, true /* rebind */));
		}
	}

	// .recreate_depth_stencil
	tcu::TestCaseGroup* recreateDepthStencilGroup = new tcu::TestCaseGroup(m_testCtx, "recreate_depth_stencil", "Recreate depth and stencil buffers");
	addChild(recreateDepthStencilGroup);
	for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); fmtNdx++)
	{
		deUint32	colorType		= GL_TEXTURE_2D;
		deUint32	colorFmt		= GL_RGBA8;
		bool		depth			= depthStencilFormats[fmtNdx].depth;
		bool		stencil			= depthStencilFormats[fmtNdx].stencil;

		// Depth and stencil: both rbo and textures
		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(objectTypes); typeNdx++)
		{
			if (!depth && objectTypes[typeNdx] != GL_RENDERBUFFER)
				continue;

			FboConfig config(GL_COLOR_BUFFER_BIT|(depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0), colorType, colorFmt, objectTypes[typeNdx], depthStencilFormats[fmtNdx].format);
			recreateDepthStencilGroup->addChild(new RecreateBuffersTest(m_context, config, (depth ? GL_DEPTH_BUFFER_BIT : 0)|(stencil ? GL_STENCIL_BUFFER_BIT : 0), true /* rebind */));
		}
	}
}

} // Functional
} // gles3
} // deqp