namespace Functional
{

static const FormatKey s_es2ColorRenderables[] =
{
    GL_RGBA4, GL_RGB5_A1, GL_RGB565,
};

// GLES2 does not strictly allow these, but this seems to be a bug in the
// specification. For now, let's assume the unsized formats corresponding to
// the color-renderable sized formats are allowed.
// See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=7333

static const FormatKey s_es2UnsizedColorRenderables[] =
{
    GLS_UNSIZED_FORMATKEY(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4),
    GLS_UNSIZED_FORMATKEY(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1),
    GLS_UNSIZED_FORMATKEY(GL_RGB, GL_UNSIGNED_SHORT_5_6_5)
};

static const FormatKey s_es2DepthRenderables[] =
{
    GL_DEPTH_COMPONENT16,
};

static const FormatKey s_es2StencilRenderables[] =
{
    GL_STENCIL_INDEX8,
};

static const FormatEntry s_es2Formats[] =
{
    {   COLOR_RENDERABLE | TEXTURE_VALID,
        GLS_ARRAY_RANGE(s_es2UnsizedColorRenderables)
    },
    {   REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
        GLS_ARRAY_RANGE(s_es2ColorRenderables)
    },
    {   REQUIRED_RENDERABLE | DEPTH_RENDERABLE | RENDERBUFFER_VALID,
        GLS_ARRAY_RANGE(s_es2DepthRenderables)
    },
    {   REQUIRED_RENDERABLE | STENCIL_RENDERABLE | RENDERBUFFER_VALID,
        GLS_ARRAY_RANGE(s_es2StencilRenderables)
    },
};

// We have here only the extensions that are redundant in vanilla GLES3. Those
// that are applicable both to GLES2 and GLES3 are in glsFboCompletenessTests.cpp.

// GL_OES_texture_float
static const FormatKey s_oesTextureFloatFormats[] =
{
    GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_FLOAT),
    GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_FLOAT),
};

// GL_OES_texture_half_float
static const FormatKey s_oesTextureHalfFloatFormats[] =
{
    GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_HALF_FLOAT_OES),
    GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_HALF_FLOAT_OES),
};

// GL_EXT_sRGB_write_control
static const FormatKey s_extSrgbWriteControlFormats[] =
{
    GL_SRGB8_ALPHA8
};

static const FormatExtEntry s_es2ExtFormats[] =
{
    // The extension does not specify these to be color-renderable.
    {
        "GL_OES_texture_float",
        TEXTURE_VALID,
        GLS_ARRAY_RANGE(s_oesTextureFloatFormats)
    },
    {
        "GL_OES_texture_half_float",
        TEXTURE_VALID,
        GLS_ARRAY_RANGE(s_oesTextureHalfFloatFormats)
    },

    // GL_EXT_sRGB_write_control makes SRGB8_ALPHA8 color-renderable
    {
        "GL_EXT_sRGB_write_control",
        REQUIRED_RENDERABLE | TEXTURE_VALID | COLOR_RENDERABLE | RENDERBUFFER_VALID,
        GLS_ARRAY_RANGE(s_extSrgbWriteControlFormats)
    },
};

class ES2Checker : public Checker
{
public:
    ES2Checker				(void) : m_width(-1), m_height(-1) {}
    void	check					(GLenum attPoint, const Attachment& att,
                                     const Image* image);
private:
    GLsizei	m_width;	//< The common width of images
    GLsizei	m_height;	//< The common height of images
};

void ES2Checker::check(GLenum attPoint, const Attachment& att, const Image* image)
{
    DE_UNREF(attPoint);
    DE_UNREF(att);
    // GLES2: "All attached images have the same width and height."
    if (m_width == -1)
    {
        m_width = image->width;
        m_height = image->height;
    }
    else
    {
        require(image->width == m_width && image->height == m_height,
                GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
    }
    // GLES2, 4.4.5: "some implementations may not support rendering to
    // particular combinations of internal formats. If the combination of
    // formats of the images attached to a framebuffer object are not
    // supported by the implementation, then the framebuffer is not complete
    // under the clause labeled FRAMEBUFFER_UNSUPPORTED."
    //
    // Hence it is _always_ allowed to report FRAMEBUFFER_UNSUPPORTED.
    canRequire(false, GL_FRAMEBUFFER_UNSUPPORTED);
}

struct FormatCombination
{
    GLenum			colorKind;
    ImageFormat		colorFmt;
    GLenum			depthKind;
    ImageFormat		depthFmt;
    GLenum			stencilKind;
    ImageFormat		stencilFmt;
};

class SupportedCombinationTest : public fboc::TestBase
{
public:
    SupportedCombinationTest	(fboc::Context& ctx,
                                 const char* name, const char* desc)
        : TestBase		(ctx, name, desc) {}

    IterateResult	iterate						(void);
    bool			tryCombination				(const FormatCombination& comb);
    GLenum			formatKind					(ImageFormat fmt);
};

bool SupportedCombinationTest::tryCombination (const FormatCombination& comb)
{
    glu::Framebuffer fbo(m_ctx.getRenderContext());
    FboBuilder builder(*fbo, GL_FRAMEBUFFER, fboc::gl(*this));

    attachTargetToNew(GL_COLOR_ATTACHMENT0,		comb.colorKind,		comb.colorFmt,
                      64, 						64,					builder);
    attachTargetToNew(GL_DEPTH_ATTACHMENT,		comb.depthKind,		comb.depthFmt,
                      64,						64,					builder);
    attachTargetToNew(GL_STENCIL_ATTACHMENT,	comb.stencilKind,	comb.stencilFmt,
                      64,						64,					builder);

    const GLenum glStatus = fboc::gl(*this).checkFramebufferStatus(GL_FRAMEBUFFER);

    return (glStatus == GL_FRAMEBUFFER_COMPLETE);
}

GLenum SupportedCombinationTest::formatKind (ImageFormat fmt)
{
    if (fmt.format == GL_NONE)
        return GL_NONE;

    const FormatFlags flags = m_ctx.getMinFormats().getFormatInfo(fmt, ANY_FORMAT);
    const bool rbo = (flags & RENDERBUFFER_VALID) != 0;
    // exactly one of renderbuffer and texture is supported by vanilla GLES2 formats
    DE_ASSERT(rbo != ((flags & TEXTURE_VALID) != 0));

    return rbo ? GL_RENDERBUFFER : GL_TEXTURE;
}

IterateResult SupportedCombinationTest::iterate (void)
{
    const FormatDB& db		= m_ctx.getMinFormats();
    const ImageFormat none	= ImageFormat::none();
    Formats colorFmts		= db.getFormats(COLOR_RENDERABLE);
    Formats depthFmts		= db.getFormats(DEPTH_RENDERABLE);
    Formats stencilFmts		= db.getFormats(STENCIL_RENDERABLE);
    FormatCombination comb;
    bool succ = false;

    colorFmts.insert(none);
    depthFmts.insert(none);
    stencilFmts.insert(none);

    for (Formats::const_iterator col = colorFmts.begin(); col != colorFmts.end(); col++)
    {
        comb.colorFmt = *col;
        comb.colorKind = formatKind(*col);
        for (Formats::const_iterator dep = depthFmts.begin(); dep != depthFmts.end(); dep++)
        {
            comb.depthFmt = *dep;
            comb.depthKind = formatKind(*dep);
            for (Formats::const_iterator stc = stencilFmts.begin();
                    stc != stencilFmts.end(); stc++)
            {
                comb.stencilFmt = *stc;
                comb.stencilKind = formatKind(*stc);
                succ = tryCombination(comb);
                if (succ)
                    break;
            }
        }
    }

    if (succ)
        pass();
    else
        fail("No supported format combination found");

    return STOP;
}

class ES2CheckerFactory : public CheckerFactory {
public:
    Checker*			createChecker	(void) {
        return new ES2Checker();
    }
};

class TestGroup : public TestCaseGroup
{
public:
    TestGroup		(Context& ctx);
    void				init			(void);
private:
    ES2CheckerFactory	m_checkerFactory;
    fboc::Context		m_fboc;
};

TestGroup::TestGroup (Context& ctx)
    : TestCaseGroup		(ctx, "completeness", "Completeness tests")
    , m_checkerFactory	()
    , m_fboc			(ctx.getTestContext(), ctx.getRenderContext(), m_checkerFactory)
{
    const FormatEntries stdRange = GLS_ARRAY_RANGE(s_es2Formats);
    const FormatExtEntries extRange = GLS_ARRAY_RANGE(s_es2ExtFormats);

    m_fboc.addFormats(stdRange);
    m_fboc.addExtFormats(extRange);
    m_fboc.setHaveMulticolorAtts(
        ctx.getContextInfo().isExtensionSupported("GL_NV_fbo_color_attachments"));
}

void TestGroup::init (void)
{
    tcu::TestCaseGroup* attCombTests = m_fboc.createAttachmentTests();
    addChild(m_fboc.createRenderableTests());
    attCombTests->addChild(new SupportedCombinationTest(
                               m_fboc,
                               "exists_supported",
                               "Test for existence of a supported combination of formats"));
    addChild(attCombTests);
    addChild(m_fboc.createSizeTests());
}

tcu::TestCaseGroup* createFboCompletenessTests (Context& context)
{
    return new TestGroup(context);
}

} // Functional
namespace Functional
{

static const FormatKey s_es3ColorRenderables[] =
{
	// GLES3, 4.4.4: "An internal format is color-renderable if it is one of
	// the formats from table 3.12 noted as color-renderable..."
	GL_R8, GL_RG8, GL_RGB8, GL_RGB565, GL_RGBA4, GL_RGB5_A1, GL_RGBA8,
	GL_RGB10_A2, GL_RGB10_A2UI, GL_SRGB8_ALPHA8,
	GL_R8I, GL_R8UI, GL_R16I, GL_R16UI, GL_R32I, GL_R32UI,
	GL_RG8I, GL_RG8UI, GL_RG16I, GL_RG16UI, GL_RG32I, GL_RG32UI,
	GL_RGBA8I, GL_RGBA8UI, GL_RGBA16I, GL_RGBA16UI, GL_RGBA32I, GL_RGBA32UI,
};

static const FormatKey s_es3UnsizedColorRenderables[] =
{
	// "...or if it is unsized format RGBA or RGB."
	// See Table 3.3 in GLES3.
	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_UNSIGNED_BYTE),
	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_UNSIGNED_SHORT_4_4_4_4),
	GLS_UNSIZED_FORMATKEY(GL_RGBA,	GL_UNSIGNED_SHORT_5_5_5_1),
	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_UNSIGNED_BYTE),
	GLS_UNSIZED_FORMATKEY(GL_RGB,	GL_UNSIGNED_SHORT_5_6_5),
};

static const FormatKey s_es3DepthRenderables[] =
{
	// GLES3, 4.4.4: "An internal format is depth-renderable if it is one of
	// the formats from table 3.13."
	GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32F,
	GL_DEPTH24_STENCIL8, GL_DEPTH32F_STENCIL8,
};

static const FormatKey s_es3StencilRboRenderables[] =
{
	// GLES3, 4.4.4: "An internal format is stencil-renderable if it is
	// STENCIL_INDEX8..."
	GL_STENCIL_INDEX8,
};

static const FormatKey s_es3StencilRenderables[] =
{
	// "...or one of the formats from table 3.13 whose base internal format is
	// DEPTH_STENCIL."
	GL_DEPTH24_STENCIL8, GL_DEPTH32F_STENCIL8,
};

static const FormatKey s_es3TextureFloatFormats[] =
{
	GL_RGBA32F, GL_RGBA16F, GL_R11F_G11F_B10F,
	GL_RG32F, GL_RG16F, GL_R32F, GL_R16F,
	GL_RGBA16F, GL_RGB16F, GL_RG16F, GL_R16F,
};

static const FormatKey s_es3NotRenderableTextureFormats[] =
{
	GL_R8_SNORM, GL_RG8_SNORM, GL_RGB8_SNORM, GL_RGBA8_SNORM,
	GL_RGB9_E5, GL_SRGB8,
	GL_RGB8I, GL_RGB16I, GL_RGB32I,
	GL_RGB8UI, GL_RGB16UI,GL_RGB32UI,
};

static const FormatEntry s_es3Formats[] =
{
	// Renderbuffers don't support unsized formats
	{ REQUIRED_RENDERABLE | COLOR_RENDERABLE | TEXTURE_VALID,
	  GLS_ARRAY_RANGE(s_es3UnsizedColorRenderables) },
	{ REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID | TEXTURE_VALID,
	  GLS_ARRAY_RANGE(s_es3ColorRenderables) },
	{ REQUIRED_RENDERABLE | DEPTH_RENDERABLE | RENDERBUFFER_VALID | TEXTURE_VALID,
	  GLS_ARRAY_RANGE(s_es3DepthRenderables) },
	{ REQUIRED_RENDERABLE | STENCIL_RENDERABLE | RENDERBUFFER_VALID,
	  GLS_ARRAY_RANGE(s_es3StencilRboRenderables) },
	{ REQUIRED_RENDERABLE | STENCIL_RENDERABLE | RENDERBUFFER_VALID | TEXTURE_VALID,
	  GLS_ARRAY_RANGE(s_es3StencilRenderables) },
	{ TEXTURE_VALID,
	  GLS_ARRAY_RANGE(s_es3NotRenderableTextureFormats) },

	// These are not color-renderable in vanilla ES3, but we need to mark them
	// as valid for textures, since EXT_color_buffer_(half_)float brings in
	// color-renderability and only renderbuffer-validity.
	{ TEXTURE_VALID,
	  GLS_ARRAY_RANGE(s_es3TextureFloatFormats) },
};

// GL_EXT_color_buffer_float
static const FormatKey s_extColorBufferFloatFormats[] =
{
	GL_RGBA32F, GL_RGBA16F, GL_R11F_G11F_B10F, GL_RG32F, GL_RG16F, GL_R32F, GL_R16F,
};

// GL_OES_texture_stencil8
static const FormatKey s_extOESTextureStencil8[] =
{
	GL_STENCIL_INDEX8,
};

// GL_EXT_render_snorm
static const FormatKey s_extRenderSnorm[] =
{
	GL_R8_SNORM, GL_RG8_SNORM, GL_RGBA8_SNORM,
};

static const FormatExtEntry s_es3ExtFormats[] =
{
	{
		"GL_EXT_color_buffer_float",
		// These are already texture-valid in ES3, the extension just adds RBO
		// support and makes them color-renderable.
		REQUIRED_RENDERABLE | COLOR_RENDERABLE | RENDERBUFFER_VALID,
		GLS_ARRAY_RANGE(s_extColorBufferFloatFormats)
	},
	{
		"GL_OES_texture_stencil8",
		// \note: es3 RBO tests actually cover the first two requirements
		// - kept here for completeness
		REQUIRED_RENDERABLE | STENCIL_RENDERABLE | TEXTURE_VALID,
		GLS_ARRAY_RANGE(s_extOESTextureStencil8)
	},

	// Since GLES31 is backwards compatible to GLES3, we might actually be running on a GLES31.
	// Add rule changes of GLES31 that have no corresponding GLES3 extension.
	//
	// \note Not all feature changes are listed here but only those that alter GLES3 subset of
	//       the formats
	{
		"DEQP_gles31_core_compatible GL_EXT_render_snorm",
		REQUIRED_RENDERABLE | COLOR_RENDERABLE | TEXTURE_VALID | RENDERBUFFER_VALID,
		GLS_ARRAY_RANGE(s_extRenderSnorm)
	},
};

class ES3Checker : public Checker
{
public:
				ES3Checker	(const glu::RenderContext& ctx)
					: Checker				(ctx)
					, m_numSamples			(-1)
					, m_depthStencilImage	(0)
					, m_depthStencilType	(GL_NONE) {}
	void		check 		(GLenum attPoint, const Attachment& att, const Image* image);

private:
	//! The common number of samples of images.
	GLsizei		m_numSamples;

	//! The common image for depth and stencil attachments.
	GLuint		m_depthStencilImage;
	GLenum		m_depthStencilType;
};

void ES3Checker::check (GLenum attPoint, const Attachment& att, const Image* image)
{
	GLsizei imgSamples = imageNumSamples(*image);

	if (m_numSamples == -1)
	{
		m_numSamples = imgSamples;
	}
	else
	{
		// GLES3: "The value of RENDERBUFFER_SAMPLES is the same for all attached
		// renderbuffers and, if the attached images are a mix of renderbuffers
		// and textures, the value of RENDERBUFFER_SAMPLES is zero."
		//
		// On creating a renderbuffer: "If _samples_ is zero, then
		// RENDERBUFFER_SAMPLES is set to zero. Otherwise [...] the resulting
		// value for RENDERBUFFER_SAMPLES is guaranteed to be greater than or
		// equal to _samples_ and no more than the next larger sample count
		// supported by the implementation."

		// Either all attachments are zero-sample renderbuffers and/or
		// textures, or none of them are.
		if ((m_numSamples == 0) != (imgSamples == 0))
			addFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, "Mixed multi- and single-sampled attachments");

		// If the attachments requested a different number of samples, the
		// implementation is allowed to report this as incomplete. However, it
		// is also possible that despite the different requests, the
		// implementation allocated the same number of samples to both. Hence
		// reporting the framebuffer as complete is also legal.
		if (m_numSamples != imgSamples)
			addPotentialFBOStatus(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, "Number of samples differ");
	}

	// "Depth and stencil attachments, if present, are the same image."
	if (attPoint == GL_DEPTH_ATTACHMENT || attPoint == GL_STENCIL_ATTACHMENT)
	{
		if (m_depthStencilImage == 0)
		{
			m_depthStencilImage = att.imageName;
			m_depthStencilType = attachmentType(att);
		}
		else
		{
			if (m_depthStencilImage != att.imageName || m_depthStencilType != attachmentType(att))
				addFBOStatus(GL_FRAMEBUFFER_UNSUPPORTED, "Depth and stencil attachments are not the same image");
		}
	}
}

struct NumLayersParams
{
	GLenum		textureKind;		//< GL_TEXTURE_3D or GL_TEXTURE_2D_ARRAY
	GLsizei		numLayers;			//< Number of layers in texture
	GLsizei		attachmentLayer;	//< Layer referenced by attachment

	static string	getName			(const NumLayersParams& params);
	static string	getDescription	(const NumLayersParams& params);
};

string NumLayersParams::getName (const NumLayersParams& params)
{
	ostringstream os;
	const string kindStr = params.textureKind == GL_TEXTURE_3D ? "3d" : "2darr";
	os << kindStr << "_" << params.numLayers << "_" << params.attachmentLayer;
	return os.str();
}

string NumLayersParams::getDescription (const NumLayersParams& params)
{
	ostringstream os;
	const string kindStr = (params.textureKind == GL_TEXTURE_3D
							? "3D Texture"
							: "2D Array Texture");
	os << kindStr + ", "
	   << params.numLayers << " layers, "
	   << "attached layer " << params.attachmentLayer << ".";
	return os.str();
}

class NumLayersTest : public fboc::ParamTest<NumLayersParams>
{
public:
					NumLayersTest		(fboc::Context& ctx, NumLayersParams param)
						: fboc::ParamTest<NumLayersParams> (ctx, param) {}

	IterateResult	build				(FboBuilder& builder);
};

IterateResult NumLayersTest::build (FboBuilder& builder)
{
	TextureLayered* texCfg = DE_NULL;
	const GLenum target = GL_COLOR_ATTACHMENT0;

	switch (m_params.textureKind)
	{
		case GL_TEXTURE_3D:
			texCfg = &builder.makeConfig<Texture3D>();
			break;
		case GL_TEXTURE_2D_ARRAY:
			texCfg = &builder.makeConfig<Texture2DArray>();
			break;
		default:
			DE_ASSERT(!"Impossible case");
	}
	texCfg->internalFormat = getDefaultFormat(target, GL_TEXTURE);
	texCfg->width = 64;
	texCfg->height = 64;
	texCfg->numLayers = m_params.numLayers;
	const GLuint tex = builder.glCreateTexture(*texCfg);

	TextureLayerAttachment* att = &builder.makeConfig<TextureLayerAttachment>();
	att->layer = m_params.attachmentLayer;
	att->imageName = tex;

	builder.glAttach(target, att);

	return STOP;
}

enum
{
	SAMPLES_NONE = -2,
	SAMPLES_TEXTURE = -1
};
struct NumSamplesParams
{
	// >= 0: renderbuffer with N samples, -1: texture, -2: no attachment
	GLsizei		numSamples[3];

	static string	getName			(const NumSamplesParams& params);
	static string	getDescription	(const NumSamplesParams& params);
};

string NumSamplesParams::getName (const NumSamplesParams& params)
{
	ostringstream os;
	bool first = true;
	for (const GLsizei* ns	=	DE_ARRAY_BEGIN(params.numSamples);
		 ns 				!=	DE_ARRAY_END(params.numSamples);
		 ns++)
	{
		if (first)
			first = false;
		else
			os << "_";

		if (*ns == SAMPLES_NONE)
			os << "none";
		else if (*ns == SAMPLES_TEXTURE)
			os << "tex";
		else
			os << "rbo" << *ns;
	}
	return os.str();
}

string NumSamplesParams::getDescription (const NumSamplesParams& params)
{
	ostringstream os;
	bool first = true;
	static const char* const s_names[] = { "color", "depth", "stencil" };
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == DE_LENGTH_OF_ARRAY(params.numSamples));

	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_names); i++)
	{
		GLsizei ns = params.numSamples[i];

		if (ns == SAMPLES_NONE)
			continue;

		if (first)
			first = false;
		else
			os << ", ";

		if (ns == SAMPLES_TEXTURE)
			os << "texture " << s_names[i] << " attachment";
		else
			os << ns << "-sample renderbuffer " << s_names[i] << " attachment";
	}
	return os.str();
}

class NumSamplesTest : public fboc::ParamTest<NumSamplesParams>
{
public:
					NumSamplesTest		(fboc::Context& ctx, NumSamplesParams param)
						: fboc::ParamTest<NumSamplesParams> (ctx, param) {}

	IterateResult	build				(FboBuilder& builder);
};

IterateResult NumSamplesTest::build (FboBuilder& builder)
{
	static const GLenum s_targets[] =
		{
			GL_COLOR_ATTACHMENT0,	GL_COLOR_ATTACHMENT1,	GL_DEPTH_ATTACHMENT,
		};
	// Non-integer formats for each attachment type.
	// \todo [2013-12-17 lauri] Add fixed/floating/integer metadata for formats so
	// we can pick one smartly or maybe try several.
	static const GLenum s_formats[] =
		{
			GL_RGBA8,				GL_RGB565,				GL_DEPTH_COMPONENT24,
		};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_targets) == DE_LENGTH_OF_ARRAY(m_params.numSamples));

	for (int i = 0; i < DE_LENGTH_OF_ARRAY(s_targets); i++)
	{
		const GLenum target = s_targets[i];
		const ImageFormat fmt = { s_formats[i], GL_NONE };

		const GLsizei ns = m_params.numSamples[i];
		if (ns == -2)
			continue;

		if (ns == -1)
		{
			attachTargetToNew(target, GL_TEXTURE, fmt, 64, 64, builder);
		}
		else
		{
			Renderbuffer& rboCfg = builder.makeConfig<Renderbuffer>();
			rboCfg.internalFormat = fmt;
			rboCfg.width = rboCfg.height = 64;
			rboCfg.numSamples = ns;

			const GLuint rbo = builder.glCreateRbo(rboCfg);
			// Implementations do not necessarily support sample sizes greater than 1.
			TCU_CHECK_AND_THROW(NotSupportedError,
								builder.getError() != GL_INVALID_OPERATION,
								"Unsupported number of samples");
			RenderbufferAttachment& att = builder.makeConfig<RenderbufferAttachment>();
			att.imageName = rbo;
			builder.glAttach(target, &att);
		}
	}

	return STOP;
}

class ES3CheckerFactory : public CheckerFactory
{
public:
	Checker*			createChecker	(const glu::RenderContext& ctx) { return new ES3Checker(ctx); }
};

class TestGroup : public TestCaseGroup
{
public:
						TestGroup		(Context& context);
	void				init			(void);
private:
	ES3CheckerFactory	m_checkerFactory;
	fboc::Context		m_fboc;
};

void TestGroup::init (void)
{
	addChild(m_fboc.createRenderableTests());
	addChild(m_fboc.createAttachmentTests());
	addChild(m_fboc.createSizeTests());

	TestCaseGroup* layerTests = new TestCaseGroup(
		getContext(), "layer", "Tests for layer attachments");

	static const NumLayersParams s_layersParams[] =
		{ //  textureKind			numLayers	attachmentKind
			{ GL_TEXTURE_2D_ARRAY,	1,			0 },
			{ GL_TEXTURE_2D_ARRAY,	1,			3 },
			{ GL_TEXTURE_2D_ARRAY,	4,			3 },
			{ GL_TEXTURE_2D_ARRAY,	4,			15 },
			{ GL_TEXTURE_3D,		1,			0 },
			{ GL_TEXTURE_3D,		1,			15 },
			{ GL_TEXTURE_3D,		4,			15 },
			{ GL_TEXTURE_3D,		64,			15 },
		};

	for (const NumLayersParams* lp	=	DE_ARRAY_BEGIN(s_layersParams);
		 lp							!=  DE_ARRAY_END(s_layersParams);
		 ++lp)
		layerTests->addChild(new NumLayersTest(m_fboc, *lp));

	addChild(layerTests);

	TestCaseGroup* sampleTests = new TestCaseGroup(
		getContext(), "samples", "Tests for multisample attachments");

	static const NumSamplesParams s_samplesParams[] =
	{
		{ { 0,					SAMPLES_NONE,		SAMPLES_NONE } },
		{ { 1,					SAMPLES_NONE,		SAMPLES_NONE } },
		{ { 2,					SAMPLES_NONE,		SAMPLES_NONE } },
		{ { 0,					SAMPLES_TEXTURE,	SAMPLES_NONE } },
		{ { 1,					SAMPLES_TEXTURE,	SAMPLES_NONE } },
		{ { 2,					SAMPLES_TEXTURE,	SAMPLES_NONE } },
		{ { 2,					1,					SAMPLES_NONE } },
		{ { 2,					2,					SAMPLES_NONE } },
		{ { 0,					0,					SAMPLES_TEXTURE } },
		{ { 1,					2,					0 } },
		{ { 2,					2,					0 } },
		{ { 1,					1,					1 } },
		{ { 1,					2,					4 } },
	};

	for (const NumSamplesParams* lp	=	DE_ARRAY_BEGIN(s_samplesParams);
		 lp							!=  DE_ARRAY_END(s_samplesParams);
		 ++lp)
		sampleTests->addChild(new NumSamplesTest(m_fboc, *lp));

	addChild(sampleTests);
}

TestGroup::TestGroup (Context& ctx)
	: TestCaseGroup		(ctx, "completeness", "Completeness tests")
	, m_checkerFactory	()
	, m_fboc			(ctx.getTestContext(), ctx.getRenderContext(), m_checkerFactory)
{
	const FormatEntries stdRange = GLS_ARRAY_RANGE(s_es3Formats);
	const FormatExtEntries extRange = GLS_ARRAY_RANGE(s_es3ExtFormats);

	m_fboc.addFormats(stdRange);
	m_fboc.addExtFormats(extRange);
	m_fboc.setHaveMulticolorAtts(true); // Vanilla ES3 has multiple color attachments
}

tcu::TestCaseGroup* createFboCompletenessTests (Context& context)
{
	return new TestGroup(context);
}

} // Functional