/*-------------------------------------------------------------------------
 * OpenGL Conformance Test Suite
 * -----------------------------
 *
 * Copyright (c) 2014-2016 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
 * \brief
 */ /*-------------------------------------------------------------------*/
#include "es31cFramebufferNoAttachmentsTests.hpp"
#include "gluContextInfo.hpp"
#include "gluDefs.hpp"
#include "gluStrUtil.hpp"
#include "glw.h"
#include "glwFunctions.hpp"
#include "tcuTestLog.hpp"

namespace glcts
{

using tcu::TestLog;
using std::string;
using std::vector;
using glcts::Context;

// I tried to find something like this, but failed
void checkErrorEqualsExpected(GLenum err, GLenum expected, const char* msg, const char* file, int line)
{
	if (err != expected)
	{
		std::ostringstream msgStr;
		msgStr << "glGetError() returned " << glu::getErrorStr(err) << ", expected " << glu::getErrorStr(expected);

		if (msg)
			msgStr << " in '" << msg << "'";

		if (err == GL_OUT_OF_MEMORY)
			throw glu::OutOfMemoryError(msgStr.str().c_str(), "", file, line);
		else
			throw glu::Error(err, msgStr.str().c_str(), "", file, line);
	}
}

#define GLU_EXPECT_ERROR(ERR, EXPECTED, MSG) checkErrorEqualsExpected((ERR), EXPECTED, MSG, __FILE__, __LINE__)

// Contains expect_fbo_status()
class FramebufferNoAttachmentsBaseCase : public TestCase
{
public:
	FramebufferNoAttachmentsBaseCase(Context& context, const char* name, const char* description);
	~FramebufferNoAttachmentsBaseCase();

protected:
	void expect_fbo_status(GLenum target, GLenum expected_status, const char* fail_message);
};

FramebufferNoAttachmentsBaseCase::FramebufferNoAttachmentsBaseCase(Context& context, const char* name,
																   const char* description)
	: TestCase(context, name, description)
{
}

FramebufferNoAttachmentsBaseCase::~FramebufferNoAttachmentsBaseCase()
{
}

// API tests
class FramebufferNoAttachmentsApiCase : public FramebufferNoAttachmentsBaseCase
{
public:
	FramebufferNoAttachmentsApiCase(Context& context, const char* name, const char* description);
	~FramebufferNoAttachmentsApiCase();

	IterateResult iterate();

private:
	void begin_fbo_no_attachments(GLenum target);
	void begin_fbo_with_multisample_renderbuffer(GLenum target);
	void begin_fbo(GLenum target, unsigned test_case);
	void end_fbo(GLenum target);

private:
	GLuint m_fbo;
	GLuint m_renderbuffer;
	GLuint m_texture;
};

FramebufferNoAttachmentsApiCase::FramebufferNoAttachmentsApiCase(Context& context, const char* name,
																 const char* description)
	: FramebufferNoAttachmentsBaseCase(context, name, description), m_fbo(0), m_renderbuffer(0), m_texture(0)
{
}

FramebufferNoAttachmentsApiCase::~FramebufferNoAttachmentsApiCase()
{
}

void FramebufferNoAttachmentsBaseCase::expect_fbo_status(GLenum target, GLenum expected_status,
														 const char* fail_message)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
	GLenum				  error;

	error = gl.getError();
	if (error != GL_NO_ERROR)
	{
		std::ostringstream msgStr;
		msgStr << "Error before glCheckFramebufferStatus() for '" << fail_message << "'\n";

		GLU_EXPECT_NO_ERROR(error, "Error before glCheckFramebufferStatus()");
	}

	TCU_CHECK_MSG(gl.checkFramebufferStatus(target) == expected_status, fail_message);

	error = gl.getError();
	if (error != GL_NO_ERROR)
	{
		std::ostringstream msgStr;
		msgStr << "Error after glCheckFramebufferStatus() for '" << fail_message << "'\n";

		GLU_EXPECT_NO_ERROR(error, "Error after glCheckFramebufferStatus()");
	}
}

void FramebufferNoAttachmentsApiCase::begin_fbo_no_attachments(GLenum target)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	gl.genFramebuffers(1, &m_fbo);
	gl.bindFramebuffer(target, m_fbo);

	// A freshly created framebuffer with no attachment is expected to be incomplete
	// until default width and height is set.
	expect_fbo_status(target, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT,
					  "error setting up framebuffer with multisample attachment");
}

void FramebufferNoAttachmentsApiCase::begin_fbo_with_multisample_renderbuffer(GLenum target)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	gl.genFramebuffers(1, &m_fbo);
	gl.bindFramebuffer(target, m_fbo);
	gl.genRenderbuffers(1, &m_renderbuffer);
	gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
	gl.renderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 101, 102);
	gl.framebufferRenderbuffer(target, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer);
	expect_fbo_status(target, GL_FRAMEBUFFER_COMPLETE, "framebuffer with an attachment should be complete");
}

void FramebufferNoAttachmentsApiCase::begin_fbo(GLenum target, unsigned test_case)
{
	switch (test_case)
	{
	case 0:
		begin_fbo_no_attachments(target);
		break;
	case 1:
		begin_fbo_with_multisample_renderbuffer(target);
		break;
	}
}

void FramebufferNoAttachmentsApiCase::end_fbo(GLenum target)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	gl.bindFramebuffer(target, 0);
	gl.deleteFramebuffers(1, &m_fbo);
	gl.deleteRenderbuffers(1, &m_renderbuffer);
	gl.deleteTextures(1, &m_texture);
	GLU_EXPECT_NO_ERROR(gl.getError(), "error deleting framebuffer / renderbuffer / texture");
	m_fbo		   = 0;
	m_renderbuffer = 0;
	m_texture	  = 0;
}

FramebufferNoAttachmentsApiCase::IterateResult FramebufferNoAttachmentsApiCase::iterate()
{
	const glw::Functions& gl   = m_context.getRenderContext().getFunctions();
	bool				  isOk = true;
	GLint				  binding;

	GLenum targets[] = {
		GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER,
		GL_FRAMEBUFFER // equivalent to DRAW_FRAMEBUFFER
	};
	GLenum bindings[] = {
		GL_DRAW_FRAMEBUFFER_BINDING, GL_READ_FRAMEBUFFER_BINDING,
		GL_FRAMEBUFFER_BINDING // equivalent to DRAW_FRAMEBUFFER_BINDING
	};
	GLenum pnames[] = { GL_FRAMEBUFFER_DEFAULT_WIDTH, GL_FRAMEBUFFER_DEFAULT_HEIGHT, GL_FRAMEBUFFER_DEFAULT_SAMPLES,
						GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS };
	GLenum enums_invalid_list[] = { GL_NOTEQUAL,
									GL_FRONT_FACE,
									GL_PACK_ROW_LENGTH,
									GL_FIXED,
									GL_LINEAR_MIPMAP_NEAREST,
									GL_RGBA4,
									GL_TEXTURE_MAX_LOD,
									GL_RG32F,
									GL_ALIASED_POINT_SIZE_RANGE,
									GL_VERTEX_ATTRIB_ARRAY_TYPE,
									GL_DRAW_BUFFER7,
									GL_MAX_COMBINED_UNIFORM_BLOCKS,
									GL_MAX_VARYING_COMPONENTS,
									GL_SRGB,
									GL_RGB8UI,
									GL_IMAGE_BINDING_NAME,
									GL_TEXTURE_2D_MULTISAMPLE,
									GL_COMPRESSED_R11_EAC,
									GL_BUFFER_DATA_SIZE };

	GLint default_values[] = { 0, 0, 0, GL_FALSE };
	GLint valid_values[]   = { 103, 104, 4, GL_TRUE };
	GLint min_values[]	 = { 0, 0, 0, -1 }; // Skip min_value test for boolean
	GLint max_values[]	 = { 0, 0, 0, -1 }; // Skip max_value test for boolean.

	unsigned num_targets			= sizeof(targets) / sizeof(GLenum);
	unsigned num_pnames				= sizeof(pnames) / sizeof(GLenum);
	unsigned num_enums_invalid_list = sizeof(enums_invalid_list) / sizeof(GLenum);

	// Check for extra pnames allowed from supported extensions.
	vector<GLenum> pnames_ext;
	if (m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader") ||
		m_context.getContextInfo().isExtensionSupported("GL_OES_geometry_shader"))
	{
		pnames_ext.push_back(GL_FRAMEBUFFER_DEFAULT_LAYERS);
	}

	// "Random" invalid enums distributed roughly evenly throughout 16bit enum number range.
	vector<GLenum> enums_invalid;
	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader") &&
		!m_context.getContextInfo().isExtensionSupported("GL_OES_geometry_shader"))
	{
		enums_invalid.push_back(GL_FRAMEBUFFER_DEFAULT_LAYERS);
	}
	for (unsigned i = 0; i < num_enums_invalid_list; ++i)
	{
		enums_invalid.push_back(enums_invalid_list[i]);
	}

	gl.getIntegerv(GL_MAX_FRAMEBUFFER_WIDTH, &max_values[0]);
	gl.getIntegerv(GL_MAX_FRAMEBUFFER_HEIGHT, &max_values[1]);
	gl.getIntegerv(GL_MAX_FRAMEBUFFER_SAMPLES, &max_values[2]);
	GLU_EXPECT_NO_ERROR(
		gl.getError(),
		"Querying GL_MAX_FRAMEBUFFER_WIDTH / GL_MAX_FRAMEBUFFER_HEIGHT / GL_MAX_FRAMEBUFFER_SAMPLES failed");

	TCU_CHECK_MSG(max_values[0] >= 2048, "GL_MAX_FRAMEBUFFER_WIDTH does not meet minimum requirements");

	TCU_CHECK_MSG(max_values[1] >= 2048, "GL_MAX_FRAMEBUFFER_HEIGHT does not meet minimum requirements");

	TCU_CHECK_MSG(max_values[2] >= 4, "GL_MAX_FRAMEBUFFER_SAMPLES does not meet minimum requirements");

	// It is valid to ask for number of samples > 0 and get
	// implementation defined value which is above the requested
	// value. We can use simple equality comparison by using
	// reported maximum number of samples in our valid value
	// set and get test.
	valid_values[2] = max_values[2];

	// Invalid target
	for (unsigned i = 0; i < enums_invalid.size(); ++i)
	{
		GLenum target   = enums_invalid[i];
		bool   is_valid = false;
		for (unsigned j = 0; j < num_targets; ++j)
		{
			if (target == targets[j])
			{
				is_valid = true;
				break;
			}
		}

		if (is_valid)
			continue;

		for (unsigned j = 0; j < num_pnames; ++j)
		{
			gl.framebufferParameteri(target, pnames[j], valid_values[j]);
			GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_ENUM,
							 "Using glFramebufferParameteri() with invalid target should set GL_INVALID_ENUM");
		}
	}

	// For all valid targets
	for (unsigned i = 0; i < num_targets; ++i)
	{
		GLenum target = targets[i];

		glGetIntegerv(bindings[i], &binding);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetIntegerv() "
										   "should not set GL error");

		// Using default framebuffer - GL_INVALID_OPERATION
		for (unsigned j = 0; j < num_pnames; ++j)
		{
			GLint  get_value = ~0;
			GLenum pname	 = pnames[j];

			gl.framebufferParameteri(target, pname, valid_values[j]);
			if (binding == 0)
			{
				GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_OPERATION,
								 "Using glFramebufferParameteri() on default framebuffer "
								 "should set GL_INVALID_OPERATION");
			}
			else
			{
				GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() "
												   "should not set GL error");
			}

			gl.getFramebufferParameteriv(target, pname, &get_value);

			if (binding == 0)
			{
				GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_OPERATION,
								 "Using glGetFramebufferParameteriv() on default framebuffer "
								 "should set GL_INVALID_OPERATION");
				TCU_CHECK_MSG(get_value == ~0, "failed call to glGetFramebufferParameteriv() "
											   "should not modify params");
			}
			else
			{
				GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() "
												   "should not set GL error");
			}
		}

		// j == 0 : fbo without attachments
		// j == 1 : fbo with a multisample attachment
		for (unsigned j = 0; j < 2; ++j)
		{
			glGetIntegerv(bindings[i], &binding);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetIntegerv() "
											   "should not set GL error");

			if (binding == 0)
			{
				//  Check FBO status of default framebuffer
				//  TODO Check presence of default framebuffer - default framebuffer is complete
				//       only if it exists
				expect_fbo_status(target, GL_FRAMEBUFFER_COMPLETE, "Default framebuffer should be complete");
			}

			//  Invalid pname - GL_INVALID_VALUE
			begin_fbo(target, j);
			for (unsigned k = 0; k < enums_invalid.size(); ++k)
			{
				GLenum pname	= enums_invalid[k];
				bool   is_valid = false;
				for (unsigned m = 0; m < num_pnames; ++m)
				{
					if (pname == pnames[m])
					{
						is_valid = true;
						break;
					}
				}

				// Ignore any pnames that are added by extensions.
				for (unsigned m = 0; m < pnames_ext.size(); ++m)
				{
					if (pname == pnames_ext[m])
					{
						is_valid = true;
						break;
					}
				}

				if (is_valid)
					continue;

				GLint get_value = ~0;

				gl.framebufferParameteri(target, pname, 0);
				GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_ENUM, "Calling glFramebufferParameteri() with invalid pname "
																 "should set GL_INVALID_ENUM");

				gl.getFramebufferParameteriv(target, pname, &get_value);
				GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_ENUM,
								 "Calling glGetFramebufferParameteriv() with invalid pname "
								 "should set GL_INVALID_ENUM");

				TCU_CHECK_MSG(get_value == ~0, "Calling glGetFramebufferParameteriv() with invalid pname "
											   "should not modify params");
			}
			end_fbo(target);

			//  Valid set and get
			begin_fbo(target, j);
			{
				for (unsigned k = 0; k < num_pnames; ++k)
				{
					GLint get_value = ~0;

					gl.framebufferParameteri(target, pnames[k], valid_values[k]);
					GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glFramebufferParameteri() "
													   "should not set GL error");

					gl.getFramebufferParameteriv(target, pnames[k], &get_value);
					GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() "
													   "should not set GL error");

					TCU_CHECK_MSG(get_value == valid_values[k],
								  "glGetFramebufferParameteriv() "
								  "should have returned the value set with glFramebufferParameteri()");
				}

				//  After valid set, check FBO status of user FBO
				expect_fbo_status(target, GL_FRAMEBUFFER_COMPLETE,
								  "Framebuffer should be complete after setting valid valid");
			}
			end_fbo(target);

			//  Negative or too large values - GL_INVALID_VALUE
			//  Also check for correct default values
			begin_fbo(target, j);
			for (unsigned k = 0; k < num_pnames; ++k)
			{
				GLint  get_value = ~0;
				GLenum pname	 = pnames[k];

				if (min_values[k] >= 0)
				{
					gl.framebufferParameteri(target, pname, min_values[k] - 1);
					GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_VALUE,
									 "Calling glFramebufferParameteri() with negative value "
									 "should set GL_INVALID_VALUE");
				}

				gl.getFramebufferParameteriv(target, pname, &get_value);
				GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() "
												   "should not set GL error");

				TCU_CHECK_MSG(get_value == default_values[k], "glGetFramebufferParameteriv() "
															  "did not return a valid default value");

				get_value = ~0;
				if (max_values[k] >= 0)
				{
					gl.framebufferParameteri(target, pname, max_values[k] + 1);
					GLU_EXPECT_ERROR(gl.getError(), GL_INVALID_VALUE,
									 "Calling glFramebufferParameteri() too large value "
									 "should set GL_INVALID_VALUE");
				}

				gl.getFramebufferParameteriv(target, pname, &get_value);
				GLU_EXPECT_NO_ERROR(gl.getError(), "Valid call to glGetFramebufferParameteriv() "
												   "should not set GL error");

				TCU_CHECK_MSG(get_value == default_values[k], "glGetFramebufferParameteriv() "
															  "did not return a valid default value");
			}
			end_fbo(target);
		}
	}

	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, isOk ? "Pass" : "Fail");
	return STOP;
}

// Draw with imageStore, validate that framebuffer
// default width and height is respected.
class FramebufferNoAttachmentsRenderCase : public FramebufferNoAttachmentsBaseCase
{
public:
	FramebufferNoAttachmentsRenderCase(Context& context, const char* name, const char* description);

	IterateResult iterate();
	void		  deinit(void);

private:
	GLuint m_program;
	GLuint m_vertex_shader;
	GLuint m_fragment_shader;
	GLuint m_vao;
	GLuint m_framebuffer;
	GLuint m_texture;
};

FramebufferNoAttachmentsRenderCase::FramebufferNoAttachmentsRenderCase(Context& context, const char* name,
																	   const char* description)
	: FramebufferNoAttachmentsBaseCase(context, name, description)
	, m_program(0)
	, m_vertex_shader(0)
	, m_fragment_shader(0)
	, m_vao(0)
	, m_framebuffer(0)
	, m_texture(0)
{
}

void FramebufferNoAttachmentsRenderCase::deinit(void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
	gl.deleteShader(m_vertex_shader);
	gl.deleteShader(m_fragment_shader);
	gl.deleteProgram(m_program);
	gl.deleteVertexArrays(1, &m_vao);
	gl.deleteTextures(1, &m_texture);
	gl.deleteFramebuffers(1, &m_framebuffer);
}

FramebufferNoAttachmentsRenderCase::IterateResult FramebufferNoAttachmentsRenderCase::iterate()
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
	int					  max_fragment_image_uniforms;
	bool				  isOk = true;

	// Check GL_MAX_FRAGMENT_IMAGE_UNIFORMS, we need at least one
	glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &max_fragment_image_uniforms);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Querying GL_MAX_FRAGMENT_IMAGE_UNIFORMS");

	if (max_fragment_image_uniforms < 1)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "GL_MAX_FRAGMENT_IMAGE_UNIFORMS<1");
		return STOP;
	}

	// Create program and VAO
	{
		const char* vs_source = "#version 310 es\n"
								"void main()\n"
								"{\n"
								"   if      (gl_VertexID == 0) gl_Position = vec4(-1, -1, 0, 1);\n"
								"   else if (gl_VertexID == 1) gl_Position = vec4(-1,  1, 0, 1);\n"
								"   else if (gl_VertexID == 2) gl_Position = vec4( 1,  1, 0, 1);\n"
								"   else                       gl_Position = vec4( 1, -1, 0, 1);\n"
								"}\n";

		const char* fs_source = "#version 310 es\n"
								"precision highp uimage2D;\n"
								"layout(r32ui) uniform uimage2D data;\n"
								"void main()\n"
								"{\n"
								"   ivec2 image_info = ivec2(gl_FragCoord.xy);\n"
								"   imageStore(data, image_info, uvec4(1, 2, 3, 4));\n"
								"}\n";

		m_program		  = gl.createProgram();
		m_vertex_shader   = gl.createShader(GL_VERTEX_SHADER);
		m_fragment_shader = gl.createShader(GL_FRAGMENT_SHADER);
		gl.shaderSource(m_vertex_shader, 1, &vs_source, NULL);
		gl.compileShader(m_vertex_shader);
		gl.attachShader(m_program, m_vertex_shader);
		gl.shaderSource(m_fragment_shader, 1, &fs_source, NULL);
		gl.compileShader(m_fragment_shader);
		gl.attachShader(m_program, m_fragment_shader);
		gl.linkProgram(m_program);
		gl.useProgram(m_program);
		gl.genVertexArrays(1, &m_vao);
		gl.bindVertexArray(m_vao);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Creating program and VAO");
	}

	// Create framebuffer with no attachments
	gl.genFramebuffers(1, &m_framebuffer);
	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_framebuffer);
	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 32);
	gl.framebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, 32);
	expect_fbo_status(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, "Creating framebuffer with no attachments");

	// Create texture and clear it, temporarily attaching to FBO
	{
		GLuint zero[] = { 0, 0, 0, 0 };
		gl.genTextures(1, &m_texture);
		gl.bindTexture(GL_TEXTURE_2D, m_texture);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 64, 64);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		gl.framebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
		gl.viewport(0, 0, 64, 64);
		gl.clearBufferuiv(GL_COLOR, 0, zero);
		gl.framebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Creating and clearing texture");
	}

	// Draw using storeImage
	gl.drawBuffers(0, NULL);
	gl.viewport(0, 0, 64, 64);
	gl.bindImageTexture(0, m_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
	gl.drawArrays(GL_TRIANGLE_FAN, 0, 4);
	gl.memoryBarrier(GL_ALL_BARRIER_BITS);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw with imageStore");

	// Read and validate texture contents
	{
		GLuint pixels[64 * 64 * 4];
		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_framebuffer);
		gl.framebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
		expect_fbo_status(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, "ReadPixels to texture for validation");
		gl.readPixels(0, 0, 64, 64, GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "ReadPixels");

		for (unsigned y = 0; y < 64; ++y)
		{
			for (unsigned x = 0; x < 64; ++x)
			{
				GLuint expected_value = (x < 32) && (y < 32) ? 1 : 0;
				GLuint value		  = pixels[(y * 64 + x) * 4];
				TCU_CHECK_MSG(value == expected_value, "Validating draw with imageStore");
			}
		}
	}

	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, isOk ? "Pass" : "Fail");
	return STOP;
}

FramebufferNoAttachmentsTests::FramebufferNoAttachmentsTests(Context& context)
	: TestCaseGroup(context, "framebuffer_no_attachments", "Framebuffer no attachments tests")
{
}

FramebufferNoAttachmentsTests::~FramebufferNoAttachmentsTests(void)
{
}

void FramebufferNoAttachmentsTests::init(void)
{
	addChild(new FramebufferNoAttachmentsApiCase(m_context, "api", "Basic API verification"));

	addChild(new FramebufferNoAttachmentsRenderCase(m_context, "render", "Rendering with imageStore"));
}

} // glcts