/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Framebuffer Object Tests.
 *
 * Notes:
 *   + Like in API tests, tcu::sgl2s::Context class is used.
 *   + ReferenceContext is used to generate reference images.
 *   + API calls can be logged \todo [pyry] Implement.
 *//*--------------------------------------------------------------------*/

#include "es2fFboRenderTest.hpp"
#include "sglrContextUtil.hpp"
#include "sglrGLContext.hpp"
#include "sglrReferenceContext.hpp"
#include "tcuSurface.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "deRandom.hpp"
#include "deString.h"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

using std::vector;
using std::string;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::RGBA;
using tcu::Surface;
using namespace glw; // GL types

namespace deqp
{
namespace gles2
{
namespace Functional
{

// Shaders.

class FlatColorShader : public sglr::ShaderProgram
{
public:
	FlatColorShader (void)
		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
								<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
								<< sglr::pdec::VertexSource(
										"attribute highp vec4 a_position;\n"
										"void main (void)\n"
										"{\n"
										"	gl_Position = a_position;\n"
										"}\n")
								<< sglr::pdec::FragmentSource(
										"uniform mediump vec4 u_color;\n"
										"void main (void)\n"
										"{\n"
										"	gl_FragColor = u_color;\n"
										"}\n"))
	{
	}

	void setColor (sglr::Context& gl, deUint32 program, const tcu::Vec4& color)
	{
		gl.useProgram(program);
		gl.uniform4fv(gl.getUniformLocation(program, "u_color"), 1, color.getPtr());
	}

	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
			packets[packetNdx]->position = rr::readVertexAttribFloat(inputs[0], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
	}

	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
	{
		const tcu::Vec4 color(m_uniforms[0].value.f4);

		DE_UNREF(packets);

		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
	}
};

class SingleTex2DShader : public sglr::ShaderProgram
{
public:
	SingleTex2DShader (void)
		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
								<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_2D)
								<< sglr::pdec::VertexSource(
										"attribute highp vec4 a_position;\n"
										"attribute mediump vec2 a_coord;\n"
										"varying mediump vec2 v_coord;\n"
										"void main (void)\n"
										"{\n"
										"	gl_Position = a_position;\n"
										"	v_coord = a_coord;\n"
										"}\n")
								<< sglr::pdec::FragmentSource(
										"uniform sampler2D u_sampler0;\n"
										"varying mediump vec2 v_coord;\n"
										"void main (void)\n"
										"{\n"
										"	gl_FragColor = texture2D(u_sampler0, v_coord);\n"
										"}\n"))
	{
	}

	void setUnit (sglr::Context& gl, deUint32 program, int unitNdx)
	{
		gl.useProgram(program);
		gl.uniform1i(gl.getUniformLocation(program, "u_sampler0"), unitNdx);
	}

	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		{
			rr::VertexPacket& packet = *packets[packetNdx];

			packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
			packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
		}
	}

	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4 v_coord = rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx);
			const float		lod		= 0.0f;

			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, this->m_uniforms[0].sampler.tex2D->sample(v_coord.x(), v_coord.y(), lod));
		}
	}

};

class MixTexturesShader : public sglr::ShaderProgram
{
public:
	MixTexturesShader (void)
		: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
								<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
								<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_2D)
								<< sglr::pdec::Uniform("u_sampler1", glu::TYPE_SAMPLER_2D)
								<< sglr::pdec::VertexSource(
										"attribute highp vec4 a_position;\n"
										"attribute mediump vec2 a_coord;\n"
										"varying mediump vec2 v_coord;\n"
										"void main (void)\n"
										"{\n"
										"	gl_Position = a_position;\n"
										"	v_coord = a_coord;\n"
										"}\n")
								<< sglr::pdec::FragmentSource(
										"uniform sampler2D u_sampler0;\n"
										"uniform sampler2D u_sampler1;\n"
										"varying mediump vec2 v_coord;\n"
										"void main (void)\n"
										"{\n"
										"	gl_FragColor = texture2D(u_sampler0, v_coord)*0.5 + texture2D(u_sampler1, v_coord)*0.5;\n"
										"}\n"))
	{
	}

	void setUnits (sglr::Context& gl, deUint32 program, int unit0, int unit1)
	{
		gl.useProgram(program);
		gl.uniform1i(gl.getUniformLocation(program, "u_sampler0"), unit0);
		gl.uniform1i(gl.getUniformLocation(program, "u_sampler1"), unit1);
	}

	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		{
			rr::VertexPacket& packet = *packets[packetNdx];

			packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
			packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
		}
	}

	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4 v_coord = rr::readVarying<float>(packets[packetNdx], context, 0, fragNdx);
			const float		lod		= 0.0f;

			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0,   this->m_uniforms[0].sampler.tex2D->sample(v_coord.x(), v_coord.y(), lod) * 0.5f
			                                                        + this->m_uniforms[1].sampler.tex2D->sample(v_coord.x(), v_coord.y(), lod) * 0.5f);
		}
	}
};

// Framebuffer config.

class FboConfig
{
public:
	FboConfig (void)
		: colorbufferType		(GL_NONE)
		, colorbufferFormat		(GL_NONE)
		, depthbufferType		(GL_NONE)
		, depthbufferFormat		(GL_NONE)
		, stencilbufferType		(GL_NONE)
		, stencilbufferFormat	(GL_NONE)
	{
	}

	std::string				getName			(void) const;

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

	GLenum					depthbufferType;		//!< GL_RENDERBUFFER
	GLenum					depthbufferFormat;

	GLenum					stencilbufferType;		//!< GL_RENDERBUFFER
	GLenum					stencilbufferFormat;

private:
	static const char*		getFormatName	(GLenum format);
};

const char* FboConfig::getFormatName (GLenum format)
{
	switch (format)
	{
		case GL_RGB:				return "rgb";
		case GL_RGBA:				return "rgba";
		case GL_ALPHA:				return "alpha";
		case GL_LUMINANCE:			return "luminance";
		case GL_LUMINANCE_ALPHA:	return "luminance_alpha";
		case GL_RGB565:				return "rgb565";
		case GL_RGB5_A1:			return "rgb5_a1";
		case GL_RGBA4:				return "rgba4";
		case GL_RGBA16F:			return "rgba16f";
		case GL_RGB16F:				return "rgb16f";
		case GL_DEPTH_COMPONENT16:	return "depth_component16";
		case GL_STENCIL_INDEX8:		return "stencil_index8";
		default:					DE_ASSERT(false); return DE_NULL;
	}
}

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

	if (colorbufferType != GL_NONE)
	{
		switch (colorbufferType)
		{
			case GL_TEXTURE_2D:			name += "tex2d_";	break;
			case GL_TEXTURE_CUBE_MAP:	name += "texcube_";	break;
			case GL_RENDERBUFFER:		name += "rbo_";		break;
			default:					DE_ASSERT(false);	break;
		}
		name += getFormatName(colorbufferFormat);
	}

	if (depthbufferType != GL_NONE)
	{
		DE_ASSERT(depthbufferType == GL_RENDERBUFFER);
		if (name.length() > 0)
			name += "_";
		name += getFormatName(depthbufferFormat);
	}

	if (stencilbufferType != GL_NONE)
	{
		DE_ASSERT(stencilbufferType == GL_RENDERBUFFER);
		if (name.length() > 0)
			name += "_";
		name += getFormatName(stencilbufferFormat);
	}

	return name;
}

class FboIncompleteException : public tcu::TestError
{
public:
						FboIncompleteException		(const FboConfig& config, GLenum reason, const char* file, int line);
	virtual				~FboIncompleteException		(void) throw() {}

	const FboConfig&	getConfig					(void) const { return m_config; }
	GLenum				getReason					(void) const { return m_reason; }

private:
	FboConfig			m_config;
	GLenum				m_reason;
};

static const char* getFboIncompleteReasonName (GLenum reason)
{
	switch (reason)
	{
		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:			return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:	return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:			return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
		case GL_FRAMEBUFFER_UNSUPPORTED:					return "GL_FRAMEBUFFER_UNSUPPORTED";
		case GL_FRAMEBUFFER_COMPLETE:						return "GL_FRAMEBUFFER_COMPLETE";
		default:											return "UNKNOWN";
	}
}

FboIncompleteException::FboIncompleteException (const FboConfig& config, GLenum reason, const char* file, int line)
	: TestError("Framebuffer is not complete", getFboIncompleteReasonName(reason), file, line)
	, m_config(config)
	, m_reason(reason)
{
}

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

	const FboConfig&	getConfig			(void) const { return m_config; }
	deUint32			getFramebuffer		(void) const { return m_framebuffer; }
	deUint32			getColorbuffer		(void) const { return m_colorbuffer; }
	deUint32			getDepthbuffer		(void) const { return m_depthbuffer; }
	deUint32			getStencilbuffer	(void) const { return m_stencilbuffer; }

	void				checkCompleteness	(void);

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

	FboConfig			m_config;
	sglr::Context&		m_context;
	deUint32			m_framebuffer;
	deUint32			m_colorbuffer;
	deUint32			m_depthbuffer;
	deUint32			m_stencilbuffer;
};

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

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

	return false;
}

static void checkColorFormatSupport (sglr::Context& context, deUint32 sizedFormat)
{
	switch (sizedFormat)
	{
		case GL_RGBA16F:
		case GL_RGB16F:
		case GL_RG16F:
		case GL_R16F:
			if (!isExtensionSupported(context, "GL_EXT_color_buffer_half_float"))
				throw tcu::NotSupportedError("GL_EXT_color_buffer_half_float is not supported");

		default:
			break;
	}
}

Framebuffer::Framebuffer (sglr::Context& context, const FboConfig& config, int width, int height, deUint32 fbo, deUint32 colorbuffer, deUint32 depthbuffer, deUint32 stencilbuffer)
	: m_config			(config)
	, m_context			(context)
	, m_framebuffer		(fbo)
	, m_colorbuffer		(colorbuffer)
	, m_depthbuffer		(depthbuffer)
	, m_stencilbuffer	(stencilbuffer)
{
	// Verify that color format is supported
	checkColorFormatSupport(context, config.colorbufferFormat);

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

	switch (m_config.colorbufferType)
	{
		case GL_TEXTURE_2D:
			if (m_colorbuffer == 0)
				context.genTextures(1, &m_colorbuffer);
			context.bindTexture(GL_TEXTURE_2D, m_colorbuffer);
			context.texImage2D(GL_TEXTURE_2D, 0, m_config.colorbufferFormat, width, height);
			context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

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

			context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorbuffer, 0);
			break;

		case GL_TEXTURE_CUBE_MAP:
			DE_ASSERT(!"TODO");
			break;

		case GL_RENDERBUFFER:
			createRbo(m_colorbuffer, m_config.colorbufferFormat, width, height);
			context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorbuffer);
			break;

		default:
			DE_ASSERT(m_config.colorbufferType == GL_NONE);
			break;
	}

	if (m_config.depthbufferType == GL_RENDERBUFFER)
	{
		createRbo(m_depthbuffer, m_config.depthbufferFormat, width, height);
		context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthbuffer);
	}
	else
		DE_ASSERT(m_config.depthbufferType == GL_NONE);

	if (m_config.stencilbufferType == GL_RENDERBUFFER)
	{
		createRbo(m_stencilbuffer, m_config.stencilbufferFormat, width, height);
		context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_stencilbuffer);
	}
	else
		DE_ASSERT(m_config.stencilbufferType == GL_NONE);

	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
}

Framebuffer::~Framebuffer (void)
{
	m_context.deleteFramebuffers(1, &m_framebuffer);
	destroyBuffer(m_colorbuffer, m_config.colorbufferType);
	destroyBuffer(m_depthbuffer, m_config.depthbufferType);
	destroyBuffer(m_stencilbuffer, m_config.stencilbufferType);
}

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

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

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

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

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

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

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

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

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

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

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

	const FboConfig&			getConfig				(void) const { return m_config; }

	static bool					isConfigSupported		(const FboConfig& config) { DE_UNREF(config); return true; }

private:
	FboConfig					m_config;
};

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

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

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

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

	tcu::Surface	gles2Frame	(width, height);
	tcu::Surface	refFrame	(width, height);
	GLenum			gles2Error;
	GLenum			refError;

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

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

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

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

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

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

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

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

	// Compare images
	const float		threshold	= 0.02f;
	bool			imagesOk	= tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, gles2Frame, threshold, tcu::COMPARE_LOG_RESULT);

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

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

	return STOP;
}

namespace FboCases
{

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

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

ColorClearsTest::ColorClearsTest (Context& context, const FboConfig& config)
	: FboRenderCase(context, config.getName().c_str(), "Color buffer clears", config)
{
}

void ColorClearsTest::render (sglr::Context& context, Surface& dst)
{
	int			width	= 128;
	int			height	= 128;
	deRandom	rnd;

	deRandom_init(&rnd, 0);

	// Create framebuffer
	Framebuffer fbo(context, getConfig(), width, height);
	fbo.checkCompleteness();

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

	// Enable scissor test.
	context.enable(GL_SCISSOR_TEST);

	// Do 10 random color clears
	for (int i = 0; i < 15; i++)
	{
		int		cX		= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % width;
		int		cY		= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % height;
		int		cWidth	= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % (width-cX);
		int		cHeight	= (int)(deRandom_getUint32(&rnd) & 0x7fffffff) % (height-cY);
		Vec4	color	= RGBA(deRandom_getUint32(&rnd)).toVec();

		context.scissor(cX, cY, cWidth, cHeight);
		context.clearColor(color.x(), color.y(), color.z(), color.w());
		context.clear(GL_COLOR_BUFFER_BIT);
	}

	// Disable scissor.
	context.disable(GL_SCISSOR_TEST);

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

		// Draw to screen
		SingleTex2DShader	shader;
		deUint32			shaderID = context.createProgram(&shader);

		shader.setUnit(context, shaderID, 0);

		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

		// Read from screen
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
	{
		// Read from fbo
		context.readPixels(dst, 0, 0, width, height);
	}
}

class IntersectingQuadsTest : public FboRenderCase
{
public:
					IntersectingQuadsTest			(Context& context, const FboConfig& config, bool npot = false);
	virtual			~IntersectingQuadsTest			(void) {}

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

	static bool		isConfigSupported				(const FboConfig& config);

private:
	int				m_fboWidth;
	int				m_fboHeight;
};

class IntersectingQuadsNpotTest : public IntersectingQuadsTest
{
public:
	IntersectingQuadsNpotTest (Context& context, const FboConfig& config)
		: IntersectingQuadsTest(context, config, true)
	{
	}
};

IntersectingQuadsTest::IntersectingQuadsTest (Context& context, const FboConfig& config, bool npot)
	: FboRenderCase	(context, (string(npot ? "npot_" : "") + config.getName()).c_str(), "Intersecting textured quads", config)
	, m_fboWidth	(npot ? 127 : 128)
	, m_fboHeight	(npot ?  95 : 128)
{
}

bool IntersectingQuadsTest::isConfigSupported (const FboConfig& config)
{
	// \note Disabled for stencil configurations since doesn't exercise stencil buffer
	return config.depthbufferType	!= GL_NONE &&
		   config.stencilbufferType	== GL_NONE;
}

void IntersectingQuadsTest::render (sglr::Context& ctx, Surface& dst)
{
	SingleTex2DShader	texShader;
	deUint32			texShaderID = ctx.createProgram(&texShader);

	deUint32 metaballsTex	= 1;
	deUint32 quadsTex		= 2;

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

	int width	= m_fboWidth;
	int height	= m_fboHeight;
	Framebuffer fbo(ctx, getConfig(), width, height);
	fbo.checkCompleteness();

	// Setup shaders
	texShader.setUnit(ctx, texShaderID, 0);

	// Draw scene
	ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	ctx.viewport(0, 0, width, height);
	ctx.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
	ctx.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	ctx.enable(GL_DEPTH_TEST);

	ctx.bindTexture(GL_TEXTURE_2D, metaballsTex);
	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(1.0f, 1.0f, 1.0f));

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

	ctx.disable(GL_DEPTH_TEST);

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

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

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

class MixTest : public FboRenderCase
{
public:
						MixTest				(Context& context, const FboConfig& config, bool npot = false);
	virtual				~MixTest			(void) {}

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

	static bool			isConfigSupported	(const FboConfig& config);

private:
	int					m_fboAWidth;
	int					m_fboAHeight;
	int					m_fboBWidth;
	int					m_fboBHeight;
};

class MixNpotTest : public MixTest
{
public:
	MixNpotTest (Context& context, const FboConfig& config)
		: MixTest(context, config, true)
	{
	}
};

MixTest::MixTest (Context& context, const FboConfig& config, bool npot)
	: FboRenderCase	(context, (string(npot ? "mix_npot_" : "mix_") + config.getName()).c_str(), "Use two fbos as sources in draw operation", config)
	, m_fboAWidth	(npot ? 127 : 128)
	, m_fboAHeight	(npot ?  95 : 128)
	, m_fboBWidth	(npot ?  55 :  64)
	, m_fboBHeight	(npot ?  63 :  64)
{
}

bool MixTest::isConfigSupported (const FboConfig& config)
{
	// \note Disabled for stencil configurations since doesn't exercise stencil buffer
	return config.colorbufferType	== GL_TEXTURE_2D &&
		   config.stencilbufferType	== GL_NONE;
}

void MixTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	singleTexShader;
	MixTexturesShader	mixShader;

	deUint32			singleTexShaderID	= context.createProgram(&singleTexShader);
	deUint32			mixShaderID			= context.createProgram(&mixShader);

	// Texture with metaballs
	deUint32 metaballsTex = 1;
	context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
	createMetaballsTex2D(context, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);

	// Setup shaders
	singleTexShader.setUnit(context, singleTexShaderID, 0);
	mixShader.setUnits(context, mixShaderID, 0, 1);

	// Fbo, quad with metaballs texture
	Framebuffer fboA(context, getConfig(), m_fboAWidth, m_fboAHeight);
	fboA.checkCompleteness();
	context.bindFramebuffer(GL_FRAMEBUFFER, fboA.getFramebuffer());
	context.viewport(0, 0, m_fboAWidth, m_fboAHeight);
	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	sglr::drawQuad(context, singleTexShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	// Fbo, color clears
	Framebuffer fboB(context, getConfig(), m_fboBWidth, m_fboBHeight);
	fboB.checkCompleteness();
	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
	context.viewport(0, 0, m_fboBWidth, m_fboBHeight);
	context.enable(GL_SCISSOR_TEST);
	context.scissor(0, 0, 32, 64);
	context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT);
	context.scissor(32, 0, 32, 64);
	context.clearColor(0.0f, 1.0f, 0.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT);
	context.disable(GL_SCISSOR_TEST);

	// Final mix op
	context.activeTexture(GL_TEXTURE0);
	context.bindTexture(GL_TEXTURE_2D, fboA.getColorbuffer());
	context.activeTexture(GL_TEXTURE1);
	context.bindTexture(GL_TEXTURE_2D, fboB.getColorbuffer());
	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
	context.viewport(0, 0, context.getWidth(), context.getHeight());
	sglr::drawQuad(context, mixShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

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

class BlendTest : public FboRenderCase
{
public:
						BlendTest			(Context& context, const FboConfig& config, bool npot = false);
	virtual				~BlendTest			(void) {}

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

	static bool			isConfigSupported	(const FboConfig& config);

private:
	int					m_fboWidth;
	int					m_fboHeight;
};

class BlendNpotTest : public BlendTest
{
public:
	BlendNpotTest (Context& context, const FboConfig& config)
		: BlendTest(context, config, true)
	{
	}
};

BlendTest::BlendTest (Context& context, const FboConfig& config, bool npot)
	: FboRenderCase	(context, (string(npot ? "blend_npot_" : "blend_") + config.getName()).c_str(), "Blend to fbo", config)
	, m_fboWidth	(npot ? 111 : 128)
	, m_fboHeight	(npot ? 122 : 128)
{
}

bool BlendTest::isConfigSupported (const FboConfig& config)
{
	// \note Disabled for stencil configurations since doesn't exercise stencil buffer
	return config.stencilbufferType	== GL_NONE;
}

void BlendTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	shader;
	deUint32			shaderID		= context.createProgram(&shader);
	int					width			= m_fboWidth;
	int					height			= m_fboHeight;
	deUint32			metaballsTex	= 1;

	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, 64, 64);

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

	shader.setUnit(context, shaderID, 0);

	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	context.viewport(0, 0, width, height);
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	context.clearColor(0.6f, 0.0f, 0.6f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	context.enable(GL_BLEND);
	context.blendEquation(GL_FUNC_ADD);
	context.blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
	context.disable(GL_BLEND);

	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
	{
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		context.readPixels(dst, 0, 0, width, height);
}

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

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

	static bool			isConfigSupported		(const FboConfig& config);
};

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

void StencilClearsTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	shader;
	deUint32			shaderID		= context.createProgram(&shader);
	int					width			= 128;
	int					height			= 128;
	deUint32			quadsTex		= 1;
	deUint32			metaballsTex	= 2;

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

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

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

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

	// Draw 2 textures with stecil tests
	context.activeTexture(GL_TEXTURE0);
	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	context.activeTexture(GL_TEXTURE1);
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);

	context.enable(GL_STENCIL_TEST);
	context.stencilFunc(GL_EQUAL, 1, 0xffffffffu);
	shader.setUnit(context, shaderID, 0);
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	context.stencilFunc(GL_EQUAL, 2, 0xffffffffu);
	shader.setUnit(context, shaderID, 1);
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	context.disable(GL_STENCIL_TEST);

	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
	{
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.activeTexture(GL_TEXTURE0);
		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		shader.setUnit(context, shaderID, 0);
		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		context.readPixels(dst, 0, 0, width, height);
}

bool StencilClearsTest::isConfigSupported (const FboConfig& config)
{
	return config.stencilbufferType != GL_NONE;
}

class StencilTest : public FboRenderCase
{
public:
						StencilTest				(Context& context, const FboConfig& config, bool npot = false);
	virtual				~StencilTest			(void) {};

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

	static bool			isConfigSupported		(const FboConfig& config);

private:
	int					m_fboWidth;
	int					m_fboHeight;
};

class StencilNpotTest : public StencilTest
{
public:
	StencilNpotTest (Context& context, const FboConfig& config)
		: StencilTest(context, config, true)
	{
	}
};

StencilTest::StencilTest (Context& context, const FboConfig& config, bool npot)
	: FboRenderCase	(context, (string(npot ? "npot_" : "") + config.getName()).c_str(), "Stencil ops", config)
	, m_fboWidth	(npot ?  99 : 128)
	, m_fboHeight	(npot ? 110 : 128)
{
}

bool StencilTest::isConfigSupported (const FboConfig& config)
{
	return config.stencilbufferType != GL_NONE;
}

void StencilTest::render (sglr::Context& ctx, Surface& dst)
{
	FlatColorShader		colorShader;
	SingleTex2DShader	texShader;
	deUint32			colorShaderID	= ctx.createProgram(&colorShader);
	deUint32			texShaderID		= ctx.createProgram(&texShader);
	int					width			= m_fboWidth;
	int					height			= m_fboHeight;
	int					texWidth		= 64;
	int					texHeight		= 64;
	deUint32			quadsTex		= 1;
	deUint32			metaballsTex	= 2;
	bool				depth			= getConfig().depthbufferType != GL_NONE;

	createQuadsTex2D(ctx, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);
	createMetaballsTex2D(ctx, metaballsTex, GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);

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

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

	// Render intersecting quads - increment stencil on depth pass
	ctx.enable(GL_DEPTH_TEST);
	ctx.enable(GL_STENCIL_TEST);
	ctx.stencilFunc(GL_ALWAYS, 0, 0xffu);
	ctx.stencilOp(GL_KEEP, GL_KEEP, GL_INCR);

	colorShader.setColor(ctx, colorShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
	sglr::drawQuad(ctx, colorShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));

	ctx.bindTexture(GL_TEXTURE_2D, quadsTex);
	texShader.setUnit(ctx, texShaderID, 0);
	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(+1.0f, +1.0f, +1.0f));

	// Draw quad with stencil test (stencil == 1 or 2 depending on depth) - decrement on stencil failure
	ctx.disable(GL_DEPTH_TEST);
	ctx.stencilFunc(GL_EQUAL, depth ? 2 : 1, 0xffu);
	ctx.stencilOp(GL_DECR, GL_KEEP, GL_KEEP);
	colorShader.setColor(ctx, colorShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
	sglr::drawQuad(ctx, colorShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(+0.5f, +0.5f, 0.0f));

	// Draw metaballs with stencil test where stencil > 1 or 2 depending on depth buffer
	ctx.bindTexture(GL_TEXTURE_2D, metaballsTex);
	ctx.stencilFunc(GL_GREATER, depth ? 1 : 2, 0xffu);
	ctx.stencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));

	ctx.disable(GL_STENCIL_TEST);

	if (fbo.getConfig().colorbufferType == GL_TEXTURE_2D)
	{
		ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);
		ctx.activeTexture(GL_TEXTURE0);
		ctx.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
		ctx.viewport(0, 0, ctx.getWidth(), ctx.getHeight());
		sglr::drawQuad(ctx, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		ctx.readPixels(dst, 0, 0, ctx.getWidth(), ctx.getHeight());
	}
	else
		ctx.readPixels(dst, 0, 0, width, height);
}

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

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

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

void SharedColorbufferTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	shader;
	deUint32			shaderID		= context.createProgram(&shader);
	int					width			= 128;
	int					height			= 128;
//	bool				depth			= getConfig().depthbufferFormat		!= GL_NONE;
	bool				stencil			= getConfig().stencilbufferFormat	!= GL_NONE;

	// Textures
	deUint32	quadsTex		= 1;
	deUint32	metaballsTex	= 2;
	createQuadsTex2D(context, quadsTex, GL_RGB, GL_UNSIGNED_BYTE, 64, 64);
	createMetaballsTex2D(context, metaballsTex, GL_RGBA, GL_UNSIGNED_BYTE, 64, 64);

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

	shader.setUnit(context, shaderID, 0);

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

	// Fbo B - don't create colorbuffer
	FboConfig cfg = getConfig();
	cfg.colorbufferType		= GL_NONE;
	cfg.colorbufferFormat	= GL_NONE;
	Framebuffer fboB(context, cfg, width, height);

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

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

		default:
			DE_ASSERT(false);
	}

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

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

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

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

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

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

	if (stencil)
	{
		FlatColorShader flatShader;
		deUint32		flatShaderID = context.createProgram(&flatShader);

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

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

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

	// Get results
	if (fboA.getConfig().colorbufferType == GL_TEXTURE_2D)
	{
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.bindTexture(GL_TEXTURE_2D, fboA.getColorbuffer());
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		context.readPixels(dst, 0, 0, width, height);
}

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

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

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

bool SharedColorbufferClearsTest::isConfigSupported (const FboConfig& config)
{
	return config.colorbufferType	!= GL_NONE &&
		   config.depthbufferType	== GL_NONE &&
		   config.stencilbufferType	== GL_NONE;
}

void SharedColorbufferClearsTest::render (sglr::Context& context, Surface& dst)
{
	int			width			= 128;
	int			height			= 128;
	deUint32	colorbuffer		= 1;

	checkColorFormatSupport(context, getConfig().colorbufferFormat);

	// Single colorbuffer
	if (getConfig().colorbufferType == GL_TEXTURE_2D)
	{
		context.bindTexture(GL_TEXTURE_2D, colorbuffer);
		context.texImage2D(GL_TEXTURE_2D, 0, getConfig().colorbufferFormat, width, height);
		context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}
	else
	{
		DE_ASSERT(getConfig().colorbufferType == GL_RENDERBUFFER);
		context.bindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
		context.renderbufferStorage(GL_RENDERBUFFER, getConfig().colorbufferFormat, width, height);
	}

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

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

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

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

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

	context.enable(GL_SCISSOR_TEST);

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

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

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

	context.disable(GL_SCISSOR_TEST);

	if (getConfig().colorbufferType == GL_TEXTURE_2D)
	{
		SingleTex2DShader	shader;
		deUint32			shaderID = context.createProgram(&shader);

		shader.setUnit(context, shaderID, 0);

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

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

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

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

bool SharedDepthbufferTest::isConfigSupported (const FboConfig& config)
{
	return config.depthbufferType == GL_RENDERBUFFER;
}

void SharedDepthbufferTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	texShader;
	FlatColorShader		colorShader;
	deUint32			texShaderID		= context.createProgram(&texShader);
	deUint32			colorShaderID	= context.createProgram(&colorShader);
	int					width			= 128;
	int					height			= 128;
	bool				stencil			= getConfig().stencilbufferType != GL_NONE;

	// Setup shaders
	texShader.setUnit(context, texShaderID, 0);
	colorShader.setColor(context, colorShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));

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

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

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

	// Fbo B
	FboConfig cfg = getConfig();
	cfg.depthbufferType		= GL_NONE;
	cfg.depthbufferFormat	= GL_NONE;
	Framebuffer fboB(context, cfg, width, height);

	// Bind depth buffer from fbo A to fbo B
	DE_ASSERT(fboA.getConfig().depthbufferType == GL_RENDERBUFFER);
	context.bindFramebuffer(GL_FRAMEBUFFER, fboB.getFramebuffer());
	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboA.getDepthbuffer());

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

	// Enable depth test.
	context.enable(GL_DEPTH_TEST);

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

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

	context.disable(GL_DEPTH_TEST);

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

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

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

		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
	{
		// Read results from fbo B
		context.readPixels(dst, 0, 0, width, height);
	}
}

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

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

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

bool TexSubImageAfterRenderTest::isConfigSupported (const FboConfig& config)
{
	return config.colorbufferType == GL_TEXTURE_2D &&
		   (config.colorbufferFormat == GL_RGB || config.colorbufferFormat == GL_RGBA) &&
		   config.depthbufferType == GL_NONE &&
		   config.stencilbufferType == GL_NONE;
}

void TexSubImageAfterRenderTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	shader;
	deUint32			shaderID	= context.createProgram(&shader);
	bool				isRGBA		= getConfig().colorbufferFormat == GL_RGBA;

	tcu::TextureLevel fourQuads(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
	tcu::fillWithRGBAQuads(fourQuads.getAccess());

	tcu::TextureLevel metaballs(tcu::TextureFormat(isRGBA ? tcu::TextureFormat::RGBA : tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
	tcu::fillWithMetaballs(metaballs.getAccess(), 5, 3);

	shader.setUnit(context, shaderID, 0);

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

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

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

	// Render to fbo
	context.viewport(0, 0, 128, 128);
	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	// Update texture using TexSubImage2D
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, 64, 64, isRGBA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, metaballs.getAccess().getDataPtr());

	// Draw to screen
	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
	context.viewport(0, 0, context.getWidth(), context.getHeight());
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
	context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
}

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

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

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

bool TexSubImageBetweenRenderTest::isConfigSupported (const FboConfig& config)
{
	return config.colorbufferType == GL_TEXTURE_2D &&
		   (config.colorbufferFormat == GL_RGB || config.colorbufferFormat == GL_RGBA) &&
		   config.depthbufferType == GL_NONE &&
		   config.stencilbufferType == GL_NONE;
}

void TexSubImageBetweenRenderTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	shader;
	deUint32			shaderID	= context.createProgram(&shader);
	bool				isRGBA		= getConfig().colorbufferFormat == GL_RGBA;

	tcu::TextureLevel fourQuads(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
	tcu::fillWithRGBAQuads(fourQuads.getAccess());

	tcu::TextureLevel metaballs(tcu::TextureFormat(isRGBA ? tcu::TextureFormat::RGBA : tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), 64, 64);
	tcu::fillWithMetaballs(metaballs.getAccess(), 5, 3);

	tcu::TextureLevel metaballs2(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 64, 64);
	tcu::fillWithMetaballs(metaballs2.getAccess(), 5, 4);

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

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

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

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

	shader.setUnit(context, shaderID, 0);

	// Render to fbo
	context.viewport(0, 0, 128, 128);
	context.bindTexture(GL_TEXTURE_2D, fourQuadsTex);
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

	// Update texture using TexSubImage2D
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	context.texSubImage2D(GL_TEXTURE_2D, 0, 32, 32, 64, 64, isRGBA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, metaballs.getAccess().getDataPtr());

	// Render again to fbo
	context.bindTexture(GL_TEXTURE_2D, metaballsTex);
	context.enable(GL_BLEND);
	context.blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
	context.disable(GL_BLEND);

	// Draw to screen
	context.bindFramebuffer(GL_FRAMEBUFFER, 0);
	context.viewport(0, 0, context.getWidth(), context.getHeight());
	context.bindTexture(GL_TEXTURE_2D, fboTex);
	sglr::drawQuad(context, shaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

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

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

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

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

void ResizeTest::render (sglr::Context& context, Surface& dst)
{
	SingleTex2DShader	texShader;
	FlatColorShader		colorShader;
	deUint32			texShaderID		= context.createProgram(&texShader);
	deUint32			colorShaderID	= context.createProgram(&colorShader);
	deUint32			quadsTex		= 1;
	deUint32			metaballsTex	= 2;
	bool				depth			= getConfig().depthbufferType	 != GL_NONE;
	bool				stencil			= getConfig().stencilbufferType	 != GL_NONE;

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

	Framebuffer fbo(context, getConfig(), 128, 128);
	fbo.checkCompleteness();

	// Setup shaders
	texShader.setUnit(context, texShaderID, 0);
	colorShader.setColor(context, colorShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0f));

	// Render quads
	context.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	context.viewport(0, 0, 128, 128);
	context.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
	context.bindTexture(GL_TEXTURE_2D, quadsTex);
	sglr::drawQuad(context, texShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));

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

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

	int newWidth	= 64;
	int newHeight	= 32;

	// Resize buffers
	switch (fbo.getConfig().colorbufferType)
	{
		case GL_TEXTURE_2D:
			context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
			context.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().colorbufferFormat, newWidth, newHeight);
			break;

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

		default:
			DE_ASSERT(false);
	}

	if (depth)
	{
		DE_ASSERT(fbo.getConfig().depthbufferType == GL_RENDERBUFFER);
		context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getDepthbuffer());
		context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().depthbufferFormat, newWidth, newHeight);
	}

	if (stencil)
	{
		DE_ASSERT(fbo.getConfig().stencilbufferType == GL_RENDERBUFFER);
		context.bindRenderbuffer(GL_RENDERBUFFER, fbo.getStencilbuffer());
		context.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().stencilbufferFormat, newWidth, newHeight);
	}

	// Render to resized fbo
	context.viewport(0, 0, newWidth, newHeight);
	context.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
	context.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	context.enable(GL_DEPTH_TEST);

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

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

	context.disable(GL_DEPTH_TEST);

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

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

	if (getConfig().colorbufferType == GL_TEXTURE_2D)
	{
		context.bindFramebuffer(GL_FRAMEBUFFER, 0);
		context.viewport(0, 0, context.getWidth(), context.getHeight());
		context.bindTexture(GL_TEXTURE_2D, fbo.getColorbuffer());
		sglr::drawQuad(context, texShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(0.5f, 0.5f, 0.0f));
		context.readPixels(dst, 0, 0, context.getWidth(), context.getHeight());
	}
	else
		context.readPixels(dst, 0, 0, newWidth, newHeight);
}

template <GLenum Buffers>
class RecreateBuffersTest : public FboRenderCase
{
public:
					RecreateBuffersTest			(Context& context, const FboConfig& config, bool rebind);
	virtual			~RecreateBuffersTest		(void) {}

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

private:
	bool			m_rebind;
};

template <GLenum Buffers>
class RecreateBuffersNoRebindTest : public RecreateBuffersTest<Buffers>
{
public:
	RecreateBuffersNoRebindTest (Context& context, const FboConfig& config)
		: RecreateBuffersTest<Buffers>(context, config, false)
	{
	}
};

template <GLenum Buffers>
class RecreateBuffersRebindTest : public RecreateBuffersTest<Buffers>
{
public:
	RecreateBuffersRebindTest (Context& context, const FboConfig& config)
		: RecreateBuffersTest<Buffers>(context, config, true)
	{
	}
};

template <GLenum Buffers>
RecreateBuffersTest<Buffers>::RecreateBuffersTest (Context& context, const FboConfig& config, bool rebind)
	: FboRenderCase		(context, (string(rebind ? "rebind_" : "no_rebind_") + config.getName()).c_str(), "Recreate buffers", config)
	, m_rebind			(rebind)
{
}

template <GLenum Buffers>
bool RecreateBuffersTest<Buffers>::isConfigSupported (const FboConfig& config)
{
	if ((Buffers & GL_COLOR_BUFFER_BIT) && config.colorbufferType == GL_NONE)
		return false;
	if ((Buffers & GL_DEPTH_BUFFER_BIT) && config.depthbufferType == GL_NONE)
		return false;
	if ((Buffers & GL_STENCIL_BUFFER_BIT) && config.stencilbufferType == GL_NONE)
		return false;
	return true;
}

template <GLenum Buffers>
void RecreateBuffersTest<Buffers>::render (sglr::Context& ctx, Surface& dst)
{
	SingleTex2DShader	texShader;
	deUint32			texShaderID		= ctx.createProgram(&texShader);
	int					width			= 128;
	int					height			= 128;
	deUint32			metaballsTex	= 1;
	deUint32			quadsTex		= 2;
	bool				stencil			= getConfig().stencilbufferType != GL_NONE;

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

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

	// Setup shader
	texShader.setUnit(ctx, texShaderID, 0);

	// Draw scene
	ctx.bindFramebuffer(GL_FRAMEBUFFER, fbo.getFramebuffer());
	ctx.viewport(0, 0, width, height);
	ctx.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
	ctx.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	ctx.enable(GL_DEPTH_TEST);

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

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

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

	if (Buffers & GL_COLOR_BUFFER_BIT)
	{
		deUint32 colorbuf = fbo.getColorbuffer();
		switch (fbo.getConfig().colorbufferType)
		{
			case GL_TEXTURE_2D:
				ctx.deleteTextures(1, &colorbuf);
				ctx.bindTexture(GL_TEXTURE_2D, colorbuf);
				ctx.texImage2D(GL_TEXTURE_2D, 0, fbo.getConfig().colorbufferFormat, width, height);
				ctx.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

				if (m_rebind)
					ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuf, 0);
				break;

			case GL_RENDERBUFFER:
				ctx.deleteRenderbuffers(1, &colorbuf);
				ctx.bindRenderbuffer(GL_RENDERBUFFER, colorbuf);
				ctx.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().colorbufferFormat, width, height);

				if (m_rebind)
					ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuf);
				break;

			default:
				DE_ASSERT(false);
		}
	}

	if (Buffers & GL_DEPTH_BUFFER_BIT)
	{
		deUint32 depthbuf = fbo.getDepthbuffer();
		DE_ASSERT(fbo.getConfig().depthbufferType == GL_RENDERBUFFER);

		ctx.deleteRenderbuffers(1, &depthbuf);
		ctx.bindRenderbuffer(GL_RENDERBUFFER, depthbuf);
		ctx.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().depthbufferFormat, width, height);

		if (m_rebind)
			ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthbuf);
	}

	if (Buffers & GL_STENCIL_BUFFER_BIT)
	{
		deUint32 stencilbuf = fbo.getStencilbuffer();
		DE_ASSERT(fbo.getConfig().stencilbufferType == GL_RENDERBUFFER);

		ctx.deleteRenderbuffers(1, &stencilbuf);
		ctx.bindRenderbuffer(GL_RENDERBUFFER, stencilbuf);
		ctx.renderbufferStorage(GL_RENDERBUFFER, fbo.getConfig().stencilbufferFormat, width, height);

		if (m_rebind)
			ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencilbuf);
	}

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

	ctx.clearColor(0.0f, 0.0f, 1.0f, 0.0f);
	ctx.clearStencil(0);
	ctx.clear(Buffers); // \note Clear only buffers that were re-created

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

	ctx.disable(GL_DEPTH_TEST);

	// Read from fbo
	ctx.readPixels(dst, 0, 0, width, height);
}

class RepeatedClearCase : public FboRenderCase
{
private:
	static FboConfig makeConfig (deUint32 format)
	{
		FboConfig cfg;
		cfg.colorbufferType		= GL_TEXTURE_2D;
		cfg.colorbufferFormat	= format;
		cfg.depthbufferType		= GL_NONE;
		cfg.stencilbufferType	= GL_NONE;
		return cfg;
	}

public:
	RepeatedClearCase (Context& context, deUint32 format)
		: FboRenderCase(context, makeConfig(format).getName().c_str(), "Repeated clears", makeConfig(format))
	{
	}

protected:
	void render (sglr::Context& ctx, Surface& dst)
	{
		const int						numRowsCols		= 4;
		const int						cellSize		= 16;
		const int						fboSizes[]		= { cellSize, cellSize*numRowsCols };

		SingleTex2DShader				fboBlitShader;
		const deUint32					fboBlitShaderID	= ctx.createProgram(&fboBlitShader);

		de::Random						rnd				(18169662);
		deUint32						fbos[]			= { 0, 0 };
		deUint32						textures[]		= { 0, 0 };

		ctx.genFramebuffers(2, &fbos[0]);
		ctx.genTextures(2, &textures[0]);

		for (int fboNdx = 0; fboNdx < DE_LENGTH_OF_ARRAY(fbos); fboNdx++)
		{
			ctx.bindTexture(GL_TEXTURE_2D, textures[fboNdx]);
			ctx.texImage2D(GL_TEXTURE_2D, 0, getConfig().colorbufferFormat, fboSizes[fboNdx], fboSizes[fboNdx], 0,
						   getConfig().colorbufferFormat, GL_UNSIGNED_BYTE, DE_NULL);
			ctx.texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
			ctx.texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
			ctx.texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
			ctx.texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_NEAREST);

			ctx.bindFramebuffer(GL_FRAMEBUFFER, fbos[fboNdx]);
			ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[fboNdx], 0);

			{
				const GLenum status = ctx.checkFramebufferStatus(GL_FRAMEBUFFER);
				if (status != GL_FRAMEBUFFER_COMPLETE)
					throw FboIncompleteException(getConfig(), status, __FILE__, __LINE__);
			}
		}

		// larger fbo bound -- clear to transparent black
		ctx.clearColor(0.0f, 0.0f, 0.0f, 0.0f);
		ctx.clear(GL_COLOR_BUFFER_BIT);

		fboBlitShader.setUnit(ctx, fboBlitShaderID, 0);
		ctx.bindTexture(GL_TEXTURE_2D, textures[0]);

		for (int cellY = 0; cellY < numRowsCols; cellY++)
		for (int cellX = 0; cellX < numRowsCols; cellX++)
		{
			const float	r	= rnd.getFloat();
			const float	g	= rnd.getFloat();
			const float	b	= rnd.getFloat();
			const float	a	= rnd.getFloat();

			ctx.bindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
			ctx.clearColor(r, g, b, a);
			ctx.clear(GL_COLOR_BUFFER_BIT);

			ctx.bindFramebuffer(GL_FRAMEBUFFER, fbos[1]);
			ctx.viewport(cellX*cellSize, cellY*cellSize, cellSize, cellSize);
			sglr::drawQuad(ctx, fboBlitShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
		}

		ctx.readPixels(dst, 0, 0, fboSizes[1], fboSizes[1]);
	}
};

} // FboCases

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

FboRenderTestGroup::~FboRenderTestGroup (void)
{
}

namespace
{

struct TypeFormatPair
{
	GLenum		type;
	GLenum		format;
};

template <typename CaseType>
void addChildVariants (deqp::gles2::TestCaseGroup* group)
{
	TypeFormatPair colorbufferConfigs[] =
	{
//		{ GL_TEXTURE_2D,	GL_ALPHA },
//		{ GL_TEXTURE_2D,	GL_LUMINANCE },
//		{ GL_TEXTURE_2D,	GL_LUMINANCE_ALPHA },
		{ GL_TEXTURE_2D,	GL_RGB },
		{ GL_TEXTURE_2D,	GL_RGBA },
		{ GL_RENDERBUFFER,	GL_RGB565 },
		{ GL_RENDERBUFFER,	GL_RGB5_A1 },
		{ GL_RENDERBUFFER,	GL_RGBA4 },
//		{ GL_RENDERBUFFER,	GL_RGBA16F },
//		{ GL_RENDERBUFFER,	GL_RGB16F }
	};
	TypeFormatPair depthbufferConfigs[] =
	{
		{ GL_NONE,			GL_NONE },
		{ GL_RENDERBUFFER,	GL_DEPTH_COMPONENT16 }
	};
	TypeFormatPair stencilbufferConfigs[] =
	{
		{ GL_NONE,			GL_NONE },
		{ GL_RENDERBUFFER,	GL_STENCIL_INDEX8 }
	};

	for (int colorbufferNdx = 0; colorbufferNdx < DE_LENGTH_OF_ARRAY(colorbufferConfigs); colorbufferNdx++)
	for (int depthbufferNdx = 0; depthbufferNdx < DE_LENGTH_OF_ARRAY(depthbufferConfigs); depthbufferNdx++)
	for (int stencilbufferNdx = 0; stencilbufferNdx < DE_LENGTH_OF_ARRAY(stencilbufferConfigs); stencilbufferNdx++)
	{
		FboConfig config;
		config.colorbufferType		= colorbufferConfigs[colorbufferNdx].type;
		config.colorbufferFormat	= colorbufferConfigs[colorbufferNdx].format;
		config.depthbufferType		= depthbufferConfigs[depthbufferNdx].type;
		config.depthbufferFormat	= depthbufferConfigs[depthbufferNdx].format;
		config.stencilbufferType	= stencilbufferConfigs[stencilbufferNdx].type;
		config.stencilbufferFormat	= stencilbufferConfigs[stencilbufferNdx].format;

		if (CaseType::isConfigSupported(config))
			group->addChild(new CaseType(group->getContext(), config));
	}
}

template <typename CaseType>
void createChildGroup (deqp::gles2::TestCaseGroup* parent, const char* name, const char* description)
{
	deqp::gles2::TestCaseGroup* tmpGroup = new deqp::gles2::TestCaseGroup(parent->getContext(), name, description);
	parent->addChild(tmpGroup);
	addChildVariants<CaseType>(tmpGroup);
}

template <GLbitfield Buffers>
void createRecreateBuffersGroup (deqp::gles2::TestCaseGroup* parent, const char* name, const char* description)
{
	deqp::gles2::TestCaseGroup* tmpGroup = new deqp::gles2::TestCaseGroup(parent->getContext(), name, description);
	parent->addChild(tmpGroup);
	addChildVariants<FboCases::RecreateBuffersRebindTest<Buffers> >		(tmpGroup);
	addChildVariants<FboCases::RecreateBuffersNoRebindTest<Buffers> >	(tmpGroup);
}

} // anonymous

void FboRenderTestGroup::init (void)
{
	createChildGroup<FboCases::ColorClearsTest>					(this, "color_clear",		"Color buffer clears");
	createChildGroup<FboCases::StencilClearsTest>				(this, "stencil_clear",		"Stencil buffer clears");

	deqp::gles2::TestCaseGroup* colorGroup = new deqp::gles2::TestCaseGroup(m_context, "color", "Color buffer tests");
	addChild(colorGroup);
	addChildVariants<FboCases::MixTest>			(colorGroup);
	addChildVariants<FboCases::MixNpotTest>		(colorGroup);
	addChildVariants<FboCases::BlendTest>		(colorGroup);
	addChildVariants<FboCases::BlendNpotTest>	(colorGroup);

	deqp::gles2::TestCaseGroup* depthGroup = new deqp::gles2::TestCaseGroup(m_context, "depth", "Depth bufer tests");
	addChild(depthGroup);
	addChildVariants<FboCases::IntersectingQuadsTest>		(depthGroup);
	addChildVariants<FboCases::IntersectingQuadsNpotTest>	(depthGroup);

	deqp::gles2::TestCaseGroup* stencilGroup = new deqp::gles2::TestCaseGroup(m_context, "stencil", "Stencil buffer tests");
	addChild(stencilGroup);
	addChildVariants<FboCases::StencilTest>		(stencilGroup);
	addChildVariants<FboCases::StencilNpotTest>	(stencilGroup);

	createChildGroup<FboCases::SharedColorbufferClearsTest>		(this, "shared_colorbuffer_clear",	"Shared colorbuffer clears");
	createChildGroup<FboCases::SharedColorbufferTest>			(this, "shared_colorbuffer",		"Shared colorbuffer tests");
	createChildGroup<FboCases::SharedDepthbufferTest>			(this, "shared_depthbuffer",		"Shared depthbuffer tests");
	createChildGroup<FboCases::ResizeTest>						(this, "resize",					"FBO resize tests");

	createRecreateBuffersGroup<GL_COLOR_BUFFER_BIT>				(this, "recreate_colorbuffer",		"Recreate colorbuffer tests");
	createRecreateBuffersGroup<GL_DEPTH_BUFFER_BIT>				(this, "recreate_depthbuffer",		"Recreate depthbuffer tests");
	createRecreateBuffersGroup<GL_STENCIL_BUFFER_BIT>			(this, "recreate_stencilbuffer",	"Recreate stencilbuffer tests");

	deqp::gles2::TestCaseGroup* texSubImageGroup = new deqp::gles2::TestCaseGroup(m_context, "texsubimage", "TexSubImage interop with FBO colorbuffer texture");
	addChild(texSubImageGroup);
	addChildVariants<FboCases::TexSubImageAfterRenderTest>		(texSubImageGroup);
	addChildVariants<FboCases::TexSubImageBetweenRenderTest>	(texSubImageGroup);

	{
		tcu::TestCaseGroup* const repeatedClearGroup = new tcu::TestCaseGroup(m_testCtx, "repeated_clear", "Repeated FBO clears");
		addChild(repeatedClearGroup);

		repeatedClearGroup->addChild(new FboCases::RepeatedClearCase(m_context, GL_RGB));
		repeatedClearGroup->addChild(new FboCases::RepeatedClearCase(m_context, GL_RGBA));
	}
}

} // Functional
} // gles2
} // deqp