text: /*-------------------------------------------------------------------------
   * OpenGL Conformance Test Suite
   * -----------------------------
   *
   * Copyright (c) 2021 Google Inc.
   * Copyright (c) 2021 The Khronos Group Inc.
   *
   * 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 glcFramebuffercompleteness.cpp
   * \brief Tests for OpenGL ES frame buffer completeness
   *//*--------------------------------------------------------------------*/
  #include "glcFramebufferCompleteness.hpp"
  #include "deInt32.h"
  #include "gluDefs.hpp"
  #include "gluContextInfo.hpp"
  #include "glwEnums.hpp"
  #include "glwFunctions.hpp"
  #include "tcuStringTemplate.hpp"
  
  #include <algorithm>
  #include <functional>
  #include <map>
  #include <memory>
  #include <sstream>
  #include <string>
  #include <vector>
  
  namespace glcts
  {
  namespace
  {
  
  using namespace glw;
  using namespace std;
  
  struct TestContext
  {
  	const glu::RenderContext&	renderContext;
  	const glw::Functions&		gl;
  	vector<GLuint>&				fboIds;
  	vector<GLuint>&				texIds;
  	vector<GLuint>&				rboIds;
  
  	void texParameteri					(GLuint texId, GLenum target, GLenum pname, GLint parameter);
  	void bindTexture					(GLenum target, GLuint texId);
  	void texImage2D						(GLenum target, GLuint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border,
  										 GLenum format, GLenum type, const void* data);
  	void texImage2D						(GLuint texId, GLenum target, GLuint level, GLint internalFormat, GLsizei width, GLsizei height,
  										 GLint border, GLenum format, GLenum type, const void* data);
  	void texImage3D						(GLuint texId, GLenum target, GLuint level, GLint internalFormat, GLsizei width, GLsizei height,
  										 GLsizei depth, GLint border, GLenum format, GLenum type, const void* data);
  	void renderbufferStorage			(GLuint rboId, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height);
  	void renderbufferStorageMultisample	(GLuint rboId, GLenum target, GLsizei samples, GLenum internalFormat,
  										 GLsizei width, GLsizei height);
  	void bindFramebuffer				(GLenum target, GLuint fboId);
  	void framebufferTexture2D			(GLenum target, GLenum attachment, GLenum textarget, GLuint texId, GLint level);
  	void framebufferTextureLayer		(GLenum target, GLenum attachment, GLuint texId, GLint level, GLint layer);
  	void framebufferRenderbuffer		(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint rboId);
  };
  
  void TestContext::texParameteri(GLuint texId, GLenum target, GLenum pname, GLint parameter)
  {
  	bindTexture(target, texId);
  	gl.texParameteri(target, pname, parameter);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexParameteri() failed");
  	bindTexture(target, 0);
  }
  
  void TestContext::bindTexture(GLenum target, GLuint texId)
  {
  	gl.bindTexture(target, texId);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture() failed");
  }
  
  void TestContext::texImage2D(GLenum target, GLuint level, GLint internalFormat, GLsizei width, GLsizei height,
  							 GLint border, GLenum format, GLenum type, const void* data)
  {
  	gl.texImage2D(target, level, internalFormat, width, height, border, format, type, data);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage2D() failed");
  }
  
  void TestContext::texImage2D(GLuint texId, GLenum target, GLuint level, GLint internalFormat, GLsizei width,
  							 GLsizei height, GLint border, GLenum format, GLenum type, const void* data)
  {
  	bindTexture(target, texId);
  	texImage2D(target, level, internalFormat, width, height, border, format, type, data);
  	bindTexture(target, 0);
  }
  
  void TestContext::texImage3D(GLuint texId, GLenum target, GLuint level, GLint internalFormat, GLsizei width,
  							 GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* data)
  {
  	bindTexture(target, texId);
  	gl.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, data);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glTexImage3D() failed");
  	bindTexture(target, 0);
  }
  
  void TestContext::renderbufferStorage(GLuint rboId, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height)
  {
  	gl.bindRenderbuffer(GL_RENDERBUFFER, rboId);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer() failed");
  	gl.renderbufferStorage(target, internalFormat, width, height);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glRenderbufferStorage() failed");
  	gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer() failed");
  }
  
  void TestContext::renderbufferStorageMultisample(GLuint rboId, GLenum target, GLsizei samples, GLenum internalFormat,
  												 GLsizei width, GLsizei height)
  {
  	gl.bindRenderbuffer(GL_RENDERBUFFER, rboId);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer() failed");
  	gl.renderbufferStorageMultisample(target, samples, internalFormat, width, height);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glRenderbufferStorageMultisample() failed");
  	gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer() failed");
  }
  
  void TestContext::bindFramebuffer(GLenum target, GLuint fboId)
  {
  	gl.bindFramebuffer(target, fboId);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer() failed");
  }
  
  void TestContext::framebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texId, GLint level)
  {
  	gl.framebufferTexture2D(target, attachment, textarget, texId, level);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glFramebufferTexture2D() failed");
  }
  
  void TestContext::framebufferTextureLayer(GLenum target, GLenum attachment, GLuint texId, GLint level, GLint layer)
  {
  	gl.framebufferTextureLayer(target, attachment, texId, level, layer);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glFramebufferTextureLayer() failed");
  }
  
  void TestContext::framebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint rboId)
  {
  	gl.framebufferRenderbuffer(target, attachment, renderbuffertarget, rboId);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glFramebufferRenderbuffer() failed");
  }
  
  typedef function<GLenum(const glu::ContextInfo& contextInfo)>		ExpectedStatusFn;
  typedef function<void(TestContext& context)>						TestFn;
  
  ExpectedStatusFn expectedStatusConstant(GLenum expectedStatus)
  {
  	return [expectedStatus] (const glu::ContextInfo&) { return expectedStatus; };
  }
  
  ExpectedStatusFn expectedStatusWithExtension(const string& extension, GLenum statusIfSupported, GLenum statusIfNotSupported)
  {
  	return [extension, statusIfSupported, statusIfNotSupported](const glu::ContextInfo &contextInfo)
  	{
  		if (contextInfo.isExtensionSupported(extension.c_str()))
  			return statusIfSupported;
  		else
  			return statusIfNotSupported;
  	};
  }
  
  struct TestStep
  {
  	TestFn				testFn;
  	ExpectedStatusFn	expectedFbStatus;
  };
  
  typedef function<void(vector<TestStep>&, TestContext& context)>		StepsGeneratorFn;
  
  struct ExtensionEntry
  {
  	string				name;
  	bool				supportedStatus;
  };
  
  struct TestParams
  {
  	string					name;
  	string					description;
  	glu::ApiType			apiType;
  	size_t					numFboIds;
  	size_t					numTexIds;
  	size_t					numRboIds;
  	vector<TestStep>		initialSteps;
  	StepsGeneratorFn		stepsGenerator;
  };
  
  const GLuint			TEXTURE_WIDTH			= 16;
  const GLuint			TEXTURE_HEIGHT			= 16;
  const GLuint			TEXTURE_DEPTH			= 16;
  
  static const GLenum		cubemapTextureTargets[]	=
  {
  	GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
  	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
  	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_TEXTURE_CUBE_MAP_POSITIVE_Z
  };
  
  const glu::ApiType		apiES30					= glu::ApiType::es(3, 0);
  const glu::ApiType		apiES31					= glu::ApiType::es(3, 1);
  const glu::ApiType		apiES32					= glu::ApiType::es(3, 2);
  
  bool isDifferentRboSampleCountsSupported(TestContext& testContext, GLint& maxSamples)
  {
  	const auto&	gl				= testContext.gl;
  	gl.getIntegerv(GL_MAX_SAMPLES, &maxSamples);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv() failed");
  
  	if (maxSamples < 4)
  		TCU_FAIL("GL_MAX_SAMPLES needs to be >= 4");
  
  	testContext.renderbufferStorageMultisample(testContext.rboIds[0], GL_RENDERBUFFER, 1, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT);
  	gl.bindRenderbuffer(GL_RENDERBUFFER, testContext.rboIds[0]);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer() failed");
  
  	GLint		minSamplesRbo;
  	gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &minSamplesRbo);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "getRenderbufferParameteriv() failed");
  	gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer() failed");
  
  	return minSamplesRbo < maxSamples;
  }
  
  bool isDifferentTextureSampleCountsSupported(TestContext& testContext, GLint& maxSamples)
  {
  	if (glu::contextSupports(testContext.renderContext.getType(), apiES31))
  	{
  		const auto&	gl				= testContext.gl;
  		GLint	maxColorSamples;
  		gl.getIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxColorSamples);
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv() failed");
  
  		GLint	maxDepthSamples;
  		gl.getIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &maxDepthSamples);
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv() failed");
  
  		maxSamples = min(maxColorSamples, maxDepthSamples);
  
  		GLuint	tempTexId;
  		gl.genTextures(1, &tempTexId);
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures() failed");
  
  		testContext.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, tempTexId);
  		gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 1, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_TRUE);
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glTexStorage2DMultisample() failed");
  
  		GLint	minSamplesTex;
  		gl.getTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &minSamplesTex);
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetTexLevelParameteriv() failed");
  
  		testContext.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
  		gl.deleteTextures(1, &tempTexId);
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures() failed");
  
  		return minSamplesTex < maxSamples;
  	}
  	return false;
  }
  
  /* Tests are defined as ordered series of steps that each expect a specific current framebuffer status
     after being executed. A new TestContext instance (parameter) is created for each test but all steps
     within a test use the same context. No code in addition to the framebuffer status check is executed
     between steps. */
  const TestParams		tests[]					=
  {
  	{
  		"incomplete_missing_attachment",														// string					name
  		"No attachments",																		// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		0,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context) {
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"incomplete_image_zero_width",															// string					name
  		"Zero width attachment image",															// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 0, TEXTURE_HEIGHT, 0,
  									   GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, DE_NULL);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"incomplete_image_zero_height",															// string					name
  		"Zero height attachment image",															// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 0, TEXTURE_WIDTH, 0,
  									   GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, DE_NULL);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"incomplete_texture_3d_layer_oob",														// string					name
  		"3D texture layer out of bounds",														// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.texImage3D(context.texIds[0], GL_TEXTURE_3D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT,
  									   TEXTURE_DEPTH, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, context.texIds[0], 0,
  													TEXTURE_DEPTH + 1);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"incomplete_texture_2d_layer_oob",														// string					name
  		"2D texture layer out of bounds",														// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
  									   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 1);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"incomplete_texture_2d_mm_layer_oob",													// string					name
  		"2D mipmapped texture layer out of bounds",												// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
  										  GL_LINEAR_MIPMAP_LINEAR);
  					context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 1, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
  									   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 1);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				[](TestContext& context)
  				{
  					const deUint32 maxMipmapLevel = deLog2Floor32(de::max(TEXTURE_WIDTH, TEXTURE_HEIGHT));
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0],
  												 maxMipmapLevel + 2);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"mutable_nbl_texture_expect_mipmap_complete",											// string					name
  		"Mutable non base level texture as framebuffer attachment must be mipmap complete",		// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					DE_ASSERT(TEXTURE_WIDTH >= 8 && TEXTURE_HEIGHT >= 8);
  
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
  									   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 1, GL_RGBA8, TEXTURE_WIDTH >> 1,
  									   TEXTURE_HEIGHT >> 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 3, GL_RGBA8, TEXTURE_WIDTH >> 3,
  									   TEXTURE_HEIGHT >> 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 3);
  					context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 1);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 2, GL_RGBA8, TEXTURE_WIDTH >> 2,
  									   TEXTURE_HEIGHT >> 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"mutable_nbl_texture_expect_cube_complete",												// string					name
  		"Mutable non base level texture as framebuffer attachment must be cube complete",		// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.texParameteri(context.texIds[0], GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 1);
  					context.texParameteri(context.texIds[0], GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, context.texIds[0]);
  					for (size_t i = 0; i < DE_LENGTH_OF_ARRAY(cubemapTextureTargets); ++i)
  					{
  						if (i % 2)
  							continue;
  						context.texImage2D(cubemapTextureTargets[i], 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA,
  										   GL_UNSIGNED_BYTE, DE_NULL);
  						context.texImage2D(cubemapTextureTargets[i], 1, GL_RGBA8, TEXTURE_WIDTH >> 1, TEXTURE_HEIGHT >> 1, 0, GL_RGBA,
  										   GL_UNSIGNED_BYTE, DE_NULL);
  					}
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
  
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cubemapTextureTargets[0],
  												 context.texIds[0], 1);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, context.texIds[0]);
  					for (size_t i = 0; i < DE_LENGTH_OF_ARRAY(cubemapTextureTargets); ++i)
  					{
  						if (i % 2 == 0)
  							continue;
  						context.texImage2D(cubemapTextureTargets[i], 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA,
  										   GL_UNSIGNED_BYTE, DE_NULL);
  						context.texImage2D(cubemapTextureTargets[i], 1, GL_RGBA8, TEXTURE_WIDTH >> 1, TEXTURE_HEIGHT >> 1, 0, GL_RGBA,
  										   GL_UNSIGNED_BYTE, DE_NULL);
  					}
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"expect_renderable_internal_format",													// string					name
  		"Color/Depth/Stencil attachment texture must have a color/depth/stencil"				// string					description
  		" renderable internal format",
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		3,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
  									   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.texImage2D(context.texIds[1], GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, TEXTURE_WIDTH,
  									   TEXTURE_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, DE_NULL);
  					context.texImage2D(context.texIds[2], GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, TEXTURE_WIDTH, TEXTURE_HEIGHT,
  									   0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, DE_NULL);
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, context.texIds[1], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, context.texIds[2], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			}
  		},
  		[](vector<TestStep>& steps, TestContext& testContext)									// StepsGeneratorFn			stepsGenerator
  		{
  			GLint	maxColorAttachmentsCount;
  			testContext.gl.getIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachmentsCount);
  			GLU_EXPECT_NO_ERROR(testContext.gl.getError(), "glGetInteger() failed");
  
  			steps.reserve(steps.size() + 2 * maxColorAttachmentsCount);
  			for (GLint i = 0; i < maxColorAttachmentsCount; ++i)
  			{
  				steps.push_back(
  					{
  						[i](TestContext& context)
  						{
  							context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
  														 context.texIds[1], 0);
  						},
  						expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  					});
  				steps.push_back(
  					{
  						[i](TestContext& context)
  						{
  							context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
  														 context.texIds[0], 0);
  						},
  						expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  					});
  			}
  		},
  	},
  	{
  		"all_rbos_expect_same_numsamples",														// string					name
  		"Same value of FRAMEBUFFER_SAMPLES for all attached render buffers",					// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		0,																						// size_t					numTexIds
  		2,																						// size_t					numRboIds
  		{},																						// vector<TestStep>			initialSteps
  		[](vector<TestStep>& steps, TestContext& testContext)									// StepsGeneratorFn			stepsGenerator
  		{
  			GLint	maxSamples;
  			if (!isDifferentRboSampleCountsSupported(testContext, maxSamples))
  				return;
  
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.renderbufferStorage(context.rboIds[0], GL_RENDERBUFFER, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT);
  						context.renderbufferStorage(context.rboIds[1], GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, TEXTURE_WIDTH,
  													TEXTURE_HEIGHT);
  
  						context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
  														context.rboIds[0]);
  						context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
  														context.rboIds[1]);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  				});
  			steps.push_back(
  				{
  					[maxSamples](TestContext& context)
  					{
  						context.renderbufferStorageMultisample(context.rboIds[0], GL_RENDERBUFFER, maxSamples, GL_RGBA8, TEXTURE_WIDTH,
  															   TEXTURE_HEIGHT);
  						context.renderbufferStorageMultisample(context.rboIds[1], GL_RENDERBUFFER, 1, GL_DEPTH24_STENCIL8,
  															   TEXTURE_WIDTH, TEXTURE_HEIGHT);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
  				});
  		},
  	},
  	{
  		"rbo_and_texture_expect_zero_numsamples",												// string					name
  		"When using mixed renderbuffer and texture attachments, the value of"					// string					description
  		" FRAMEBUFFER_SAMPLES needs to be zero for all attached renderbuffers",
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		2,																						// size_t					numTexIds
  		2,																						// size_t	u				numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.renderbufferStorage(context.rboIds[0], GL_RENDERBUFFER, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT);
  					context.texImage2D(context.texIds[1], GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, TEXTURE_WIDTH,
  									   TEXTURE_HEIGHT, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, DE_NULL);
  
  					context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
  													context.rboIds[0]);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
  												 context.texIds[1], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.renderbufferStorage(context.rboIds[1], GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, TEXTURE_WIDTH,
  												TEXTURE_HEIGHT);
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
  									   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 0);
  					context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
  													context.rboIds[1]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.renderbufferStorageMultisample(context.rboIds[1], GL_RENDERBUFFER, 2, GL_DEPTH24_STENCIL8,
  														   TEXTURE_WIDTH, TEXTURE_HEIGHT);
  				},
  				expectedStatusWithExtension("GL_NV_framebuffer_mixed_samples", GL_FRAMEBUFFER_COMPLETE, GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.renderbufferStorageMultisample(context.rboIds[0], GL_RENDERBUFFER, 3, GL_RGBA8, TEXTURE_WIDTH,
  														   TEXTURE_HEIGHT);
  
  					context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
  													context.rboIds[0]);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
  												 context.texIds[1], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
  			},
  			{
  				[](TestContext& context)
  				{
  					context.renderbufferStorageMultisample(context.rboIds[0], GL_RENDERBUFFER, 0, GL_RGBA8, TEXTURE_WIDTH,
  														   TEXTURE_HEIGHT);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"expect_equal_numsamples",																// string					name
  		"The value of samples for each attached target must be equal",							// string					description
  		apiES31,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		4,																						// size_t					numTexIds
  		2,																						// size_t					numRboIds
  		{},																						// vector<TestStep>			initialSteps
  		[](vector<TestStep>& steps, TestContext& testContext)									// StepsGeneratorFn			stepsGenerator
  		{
  			GLint	maxRboSamples, maxTextureSamples;
  			if (!isDifferentRboSampleCountsSupported(testContext, maxRboSamples) || !isDifferentTextureSampleCountsSupported(testContext, maxTextureSamples))
  				return;
  
  			steps.push_back(
  				{
  					[maxRboSamples, maxTextureSamples](TestContext& context)
  					{
  						// Set up textures and renderbuffers for all following steps, complete = (tex0, rbo1) or (tex1, rbo0) */
  						context.renderbufferStorageMultisample(context.rboIds[0], GL_RENDERBUFFER, maxRboSamples, GL_RGBA8,
  															   TEXTURE_WIDTH, TEXTURE_HEIGHT);
  						context.renderbufferStorageMultisample(context.rboIds[1], GL_RENDERBUFFER, 1, GL_DEPTH24_STENCIL8,
  															   TEXTURE_WIDTH, TEXTURE_HEIGHT);
  
  						const auto&	gl	= context.gl;
  						context.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, context.texIds[0]);
  						gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 1, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_TRUE);
  						GLU_EXPECT_NO_ERROR(gl.getError(), "glTexStorage2DMultisample() failed");
  
  						context.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, context.texIds[1]);
  						gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 1, GL_DEPTH24_STENCIL8, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_TRUE);
  						GLU_EXPECT_NO_ERROR(gl.getError(), "glTexStorage2DMultisample() failed");
  
  						context.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, context.texIds[2]);
  						gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, maxTextureSamples, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_TRUE);
  						GLU_EXPECT_NO_ERROR(gl.getError(), "glTexStorage2DMultisample() failed");
  
  						context.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, context.texIds[3]);
  						gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, maxTextureSamples, GL_DEPTH24_STENCIL8, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_TRUE);
  						GLU_EXPECT_NO_ERROR(gl.getError(), "glTexStorage2DMultisample() failed");
  
  						context.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
  
  						// Framebuffer binding for rest of this test
  						context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
  													 context.texIds[0], 0);
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE,
  													 context.texIds[1], 0);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE,
  													 context.texIds[3], 0);
  					},
  					expectedStatusWithExtension("GL_NV_framebuffer_mixed_samples", GL_FRAMEBUFFER_COMPLETE, GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
  														context.rboIds[1]);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
  														context.rboIds[0]);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
  													 context.texIds[2], 0);
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE,
  													 context.texIds[3], 0);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						const auto&	gl	= context.gl;
  						gl.deleteTextures(1, &context.texIds[0]);
  						GLU_EXPECT_NO_ERROR(context.gl.getError(), "glDeleteTextures() failed");
  						gl.genTextures(1, &context.texIds[0]);
  						GLU_EXPECT_NO_ERROR(context.gl.getError(), "glGenTextures() failed");
  						context.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, context.texIds[0]);
  						gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 1, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_FALSE);
  						GLU_EXPECT_NO_ERROR(context.gl.getError(), "glTexStorage2DMultisample() failed");
  						context.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
  													 context.texIds[0], 0);
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE,
  													 context.texIds[1], 0);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
  				});
  		},																						// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"status_tracking",																		// string					name
  		"Modifying framebuffer attached objects correctly updates the fbo status",				// string					description
  		apiES30,																				// glu::ApiType				apiType
  		3,																						// size_t					numFboIds
  		2,																						// size_t					numTexIds
  		1,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				// Initial status -> missing_attachment
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				// Allocate and attach texture -> complete
  				[](TestContext& context)
  				{
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
  									   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				// Detach texture from fbo -> missing_attachment
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				// Allocate and attach renderbuffer -> complete
  				[](TestContext& context)
  				{
  					context.renderbufferStorage(context.rboIds[0], GL_RENDERBUFFER, GL_RGBA8, TEXTURE_WIDTH,
  												TEXTURE_HEIGHT);
  					context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
  													context.rboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				// Detach renderbuffer -> incomplete
  				[](TestContext& context)
  				{
  					context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				// Switch to incomplete fb -> missing_attachment
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[1]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				// Attach texture to fbo -> complete
  				[](TestContext& context)
  				{
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				// Change image format of attached texture -> incomplete_attachment
  				[](TestContext& context)
  				{
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, TEXTURE_WIDTH,
  									   TEXTURE_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, DE_NULL);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				// Change image format (tex storage) -> complete
  				[](TestContext& context)
  				{
  					context.bindTexture(GL_TEXTURE_2D, context.texIds[0]);
  					context.gl.texStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT);
  					GLU_EXPECT_NO_ERROR(context.gl.getError(), "glTexStorage2D() failed");
  					context.bindTexture(GL_TEXTURE_2D, 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				// Delete image -> missing_attachment
  				[](TestContext& context)
  				{
  					context.gl.deleteTextures(1, &context.texIds[0]);
  					GLU_EXPECT_NO_ERROR(context.gl.getError(), "glDeleteTextures() failed");
  					context.texIds.erase(context.texIds.begin());
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			},
  			{
  				// Recreate image in wrong format, attach to color attachment -> incomplete_attachment
  				[](TestContext& context)
  				{
  					const auto&	gl		= context.gl;
  					GLuint		texId;
  					gl.genTextures(1, &texId);
  					GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures() failed");
  					context.texIds.push_back(texId);
  
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, TEXTURE_WIDTH,
  									   TEXTURE_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, DE_NULL);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				// Format to rgba8 using copyTexImage2D from compatible fbo -> framebuffer_complete
  				[](TestContext& context)
  				{
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[2]);
  					context.texImage2D(context.texIds[1], GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
  					context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[1], 0);
  
  					context.bindTexture(GL_TEXTURE_2D, context.texIds[0]);
  					const auto&	gl	= context.gl;
  					gl.copyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0);
  					GLU_EXPECT_NO_ERROR(gl.getError(), "glCopyTexImage2D() failed");
  
  					context.bindTexture(GL_TEXTURE_2D, 0);
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				// Change currently attached texture's format to compressed tex image -> incomplete_attachment (non color renderable)
  				[](TestContext& context)
  				{
  					DE_ASSERT(TEXTURE_WIDTH == 16 && TEXTURE_HEIGHT == 16);
  					static const glw::GLubyte textureDataETC2[] = // 16x16 all black RGBA8 texture in ETC2 format
  					{
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00,
  						0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x00
  					};
  					const auto&	gl	= context.gl;
  					context.bindTexture(GL_TEXTURE_2D, context.texIds[0]);
  
  					gl.compressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, TEXTURE_WIDTH, TEXTURE_HEIGHT,
  											0, DE_LENGTH_OF_ARRAY(textureDataETC2), textureDataETC2);
  					GLU_EXPECT_NO_ERROR(gl.getError(), "glCompressedTexImage2D() failed");
  
  					context.bindTexture(GL_TEXTURE_2D, 0);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				// Re-attach rbo0 -> complete
  				[](TestContext& context)
  				{
  					context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
  													context.rboIds[0]);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE)
  			},
  			{
  				// Rbo storage to non renderable format -> incomplete_attachment
  				[](TestContext& context)
  				{
  					context.renderbufferStorage(context.rboIds[0], GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, TEXTURE_WIDTH,
  												TEXTURE_HEIGHT);
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
  			},
  			{
  				// Delete rbo -> missing_attachment
  				[](TestContext& context)
  				{
  					context.gl.deleteRenderbuffers(1, &context.rboIds[0]);
  					GLU_EXPECT_NO_ERROR(context.gl.getError(), "glDeleteRenderbuffers() failed");
  					context.rboIds.erase(context.rboIds.begin());
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"mutable_texture_missing_attachment_level",												// string					name
  		"Attaching a mutable texture with undefined image for attachment level"
  		" should be invalid",																	// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{},																						// vector<TestStep>			initialSteps
  		[](vector<TestStep>& steps, TestContext&)												// StepsGeneratorFn			stepsGenerator
  		{
  			DE_ASSERT(TEXTURE_WIDTH >= 16 && TEXTURE_HEIGHT >= 16);
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  
  						context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  						context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
  										   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  						context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 1, GL_RGBA8, TEXTURE_WIDTH >> 1, TEXTURE_HEIGHT >> 1, 0,
  										   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  						context.texImage2D(context.texIds[0], GL_TEXTURE_2D, 3, GL_RGBA8, TEXTURE_WIDTH >> 3, TEXTURE_HEIGHT >> 3, 0,
  										   GL_RGBA, GL_UNSIGNED_BYTE, DE_NULL);
  
  						context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
  
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 2);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT),
  				});
  		}
  	},
  	{
  		"immutable_texture_any_level_as_attachment",											// string					name
  		"Any level of immutable texture as attachment should be valid",							// string					description
  		apiES30,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{},																						// vector<TestStep>			initialSteps
  		[](vector<TestStep>& steps, TestContext&)												// StepsGeneratorFn			stepsGenerator
  		{
  			DE_ASSERT(TEXTURE_WIDTH >= 8 && TEXTURE_HEIGHT >= 8);
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  						context.bindTexture(GL_TEXTURE_2D, context.texIds[0]);
  						const auto&	gl	= context.gl;
  						gl.texStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT);
  						GLU_EXPECT_NO_ERROR(gl.getError(), "glTexStorage2D() failed");
  						context.bindTexture(GL_TEXTURE_2D, 0);
  
  						context.texParameteri(context.texIds[0], GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
  
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 2);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE),
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 1);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE),
  				});
  			steps.push_back(
  				{
  					[](TestContext& context)
  					{
  						context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, context.texIds[0], 0);
  					},
  					expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE),
  				});
  		}
  	},
  	{
  		"cube_map_layered_attachment_valid_size_and_format",									// string					name
  		"Cube map attachment with images of same size, same format and square should be valid",	// string					description
  		apiES32,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, context.texIds[0]);
  					for(const auto target : cubemapTextureTargets)
  						context.texImage2D(target, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
  
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					context.gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, context.texIds[0], 0);
  					GLU_EXPECT_NO_ERROR(context.gl.getError(), "glFramebufferTexture() failed");
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_COMPLETE),
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"cube_map_layered_attachment_different_formats",										// string					name
  		"Cube map attachment with images of the same size, square but different formats"
  		" should be incomplete",																// string					description
  		apiES32,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, context.texIds[0]);
  					for(size_t i = 0; i < DE_LENGTH_OF_ARRAY(cubemapTextureTargets); ++i)
  						context.texImage2D(cubemapTextureTargets[i], 0, (i % 2) ? GL_RGBA8 : GL_RGB8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, (i % 2) ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, 0);
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
  
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					context.gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, context.texIds[0], 0);
  					GLU_EXPECT_NO_ERROR(context.gl.getError(), "glFramebufferTexture() failed");
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT),
  			},
  			{
  				[](TestContext& context)
  				{
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, context.texIds[0]);
  					for(size_t i = 0; i < DE_LENGTH_OF_ARRAY(cubemapTextureTargets); ++i)
  						context.texImage2D(cubemapTextureTargets[i], 0, (i % 2) ? GL_RGBA8 : GL_SRGB8_ALPHA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
  
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					context.gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, context.texIds[0], 0);
  					GLU_EXPECT_NO_ERROR(context.gl.getError(), "glFramebufferTexture() failed");
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT),
  			}
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	},
  	{
  		"cube_map_layered_attachment_different_sizes",											// string					name
  		"Cube map with images of different sizes, same format and all square should"
  		" be incomplete",																		// string					description
  		apiES32,																				// glu::ApiType				apiType
  		1,																						// size_t					numFboIds
  		1,																						// size_t					numTexIds
  		0,																						// size_t					numRboIds
  		{																						// vector<TestStep>			initialSteps
  			{
  				[](TestContext& context)
  				{
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, context.texIds[0]);
  					for(size_t i = 0; i < DE_LENGTH_OF_ARRAY(cubemapTextureTargets); ++i)
  						context.texImage2D(cubemapTextureTargets[i], 0, GL_RGBA8, (i % 2) ? TEXTURE_WIDTH : TEXTURE_WIDTH / 2, (i % 2) ? TEXTURE_HEIGHT : TEXTURE_HEIGHT / 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
  					context.bindTexture(GL_TEXTURE_CUBE_MAP, 0);
  
  					context.bindFramebuffer(GL_FRAMEBUFFER, context.fboIds[0]);
  					context.gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, context.texIds[0], 0);
  					GLU_EXPECT_NO_ERROR(context.gl.getError(), "glFramebufferTexture() failed");
  				},
  				expectedStatusConstant(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT),
  			},
  		},
  		DE_NULL,																				// StepsGeneratorFn			stepsGenerator
  	}
  };
  
  class FramebufferCompletenessTestCase : public deqp::TestCase
  {
  public:
  	FramebufferCompletenessTestCase				(deqp::Context& context, const TestParams& params);
  	virtual ~FramebufferCompletenessTestCase	();
  
  	virtual void			init				(void);
  	virtual void			deinit				(void);
  	TestNode::IterateResult iterate				(void);
  
  private:
  	bool verifyFramebufferStatus				(const glw::Functions& gl, const ExpectedStatusFn expectedStatusFn, const size_t stepIndex);
  
  	const TestParams		m_params;
  	vector<GLuint>			m_fboIds;
  	vector<GLuint>			m_texIds;
  	vector<GLuint>			m_rboIds;
  };
  
  FramebufferCompletenessTestCase::FramebufferCompletenessTestCase(deqp::Context& context, const TestParams& params)
  	: deqp::TestCase(context, params.name.c_str(), params.description.c_str()), m_params(params)
  {
  }
  
  FramebufferCompletenessTestCase::~FramebufferCompletenessTestCase()
  {
  }
  
  void FramebufferCompletenessTestCase::init(void)
  {
  	const auto& renderContext	= m_context.getRenderContext();
  	const auto& gl				= renderContext.getFunctions();
  
  	if (m_params.numFboIds > 0)
  	{
  		m_fboIds.resize(m_params.numFboIds);
  		gl.genFramebuffers(m_params.numFboIds, m_fboIds.data());
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenFramebuffers() failed");
  	}
  	if (m_params.numTexIds > 0)
  	{
  		m_texIds.resize(m_params.numTexIds);
  		gl.genTextures(m_params.numTexIds, m_texIds.data());
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures() failed");
  	}
  	if (m_params.numRboIds > 0)
  	{
  		m_rboIds.resize(m_params.numRboIds);
  		gl.genRenderbuffers(m_params.numRboIds, m_rboIds.data());
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glGenRenderbuffers() failed");
  	}
  }
  
  void FramebufferCompletenessTestCase::deinit(void)
  {
  	const auto& renderContext	= m_context.getRenderContext();
  	const auto& gl				= renderContext.getFunctions();
  
  	if (!m_rboIds.empty())
  	{
  		gl.deleteRenderbuffers(m_params.numRboIds, m_rboIds.data());
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteRenderbuffers() failed");
  		m_rboIds.clear();
  	}
  	if (!m_texIds.empty())
  	{
  		gl.deleteTextures(m_params.numTexIds, m_texIds.data());
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteTextures() failed");
  		m_texIds.clear();
  	}
  	if (!m_fboIds.empty())
  	{
  		gl.deleteFramebuffers(m_params.numFboIds, m_fboIds.data());
  		GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteFramebufers() failed");
  		m_fboIds.clear();
  	}
  }
  
  tcu::TestNode::IterateResult FramebufferCompletenessTestCase::iterate(void)
  {
  	const auto& renderContext	= m_context.getRenderContext();
  	const auto& gl				= renderContext.getFunctions();
  	TestContext context			=
  	{
  		renderContext,	// const glu::RenderContext&	renderContext
  		gl,				// const glw::Functions&		gl
  		m_fboIds,		// vector<GLuint>&				fboIds
  		m_texIds,		// vector<GLuint>&				texIds
  		m_rboIds		// vector<GLuint>&				rboIds
  	};
  	auto steps = vector<TestStep>(m_params.initialSteps);
  	if (m_params.stepsGenerator != DE_NULL)
  		m_params.stepsGenerator(steps, context);
  
  	if (steps.empty())
  	{
  		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Pass");
  		return STOP;
  	}
  
  	size_t stepIndex = 0;
  	for (const auto& step : steps)
  	{
  		step.testFn(context);
  
  		if (!verifyFramebufferStatus(gl, step.expectedFbStatus, stepIndex++))
  			return STOP;
  	}
  	return STOP;
  }
  
  bool FramebufferCompletenessTestCase::verifyFramebufferStatus(const glw::Functions& gl, const ExpectedStatusFn expectedStatusFn,
  												  const size_t stepIndex)
  {
  	static const map<GLenum, string>	statusNames	=
  	{
  		{ GL_FRAMEBUFFER_COMPLETE						, "GL_FRAMEBUFFER_COMPLETE" },
  		{ GL_FRAMEBUFFER_UNDEFINED						, "GL_FRAMEBUFFER_UNDEFINED" },
  		{ GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT			, "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT" },
  		{ GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT	, "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT" },
  		{ GL_FRAMEBUFFER_UNSUPPORTED					, "GL_FRAMEBUFFER_UNSUPPORTED" },
  		{ GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE			, "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE" },
  		{ GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS			, "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS" }
  	};
  	const auto expectedStatus	= expectedStatusFn(m_context.getContextInfo());
  	const auto fboStatus		= gl.checkFramebufferStatus(GL_FRAMEBUFFER);
  	GLU_EXPECT_NO_ERROR(gl.getError(), "glCheckFramebufferStatus() failed");
  	if (fboStatus != expectedStatus)
  	{
  		ostringstream					msg;
  		const auto&						fboStatusName		= statusNames.find(fboStatus);
  		const auto&						expectedStatusName	= statusNames.find(expectedStatus);
  		msg << "Frame buffer status ("
  			<< ((fboStatusName != statusNames.end()) ? fboStatusName->second : std::to_string(fboStatus))
  			<< ") does not match the expected status ("
  			<< ((expectedStatusName != statusNames.end()) ? expectedStatusName->second : std::to_string(expectedStatus))
  			<< ") after step " << stepIndex;
  		TCU_FAIL(msg.str().c_str());
  		return false;
  	}
  	m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Pass");
  	return true;
  }
  
  } // namespace
  
  FramebufferCompletenessTests::FramebufferCompletenessTests(deqp::Context& context)
  	: deqp::TestCaseGroup(context, "framebuffer_completeness", "Tests for frame buffer completeness")
  {
  }
  
  void FramebufferCompletenessTests::init(void)
  {
  	const auto& renderContext	= m_context.getRenderContext();
  	for (const auto& test : tests)
  	{
  		if (!glu::contextSupports(renderContext.getType(), test.apiType))
  			continue;
  
  		addChild(new FramebufferCompletenessTestCase(m_context, test));
  	}
  }
  
  } // namespace es3cts
  