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

#include "es3fShaderFragDataTests.hpp"

#include "glsShaderLibrary.hpp"

#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluDrawUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluObjectWrapper.hpp"

#include "tcuRenderTarget.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"

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

namespace deqp
{
namespace gles3
{
namespace Functional
{

using std::string;
using tcu::TestLog;

namespace
{

enum IndexExprType
{
	INDEX_EXPR_STATIC	= 0,
	INDEX_EXPR_UNIFORM,
	INDEX_EXPR_DYNAMIC,

	INDEX_EXPR_TYPE_LAST
};

static bool compareSingleColor (tcu::TestLog& log, const tcu::Surface& surface, tcu::RGBA expectedColor, tcu::RGBA threshold)
{
	const int	maxPrints			= 10;
	int			numFailedPixels		= 0;

	log << TestLog::Message << "Expecting " << expectedColor << " with threshold " << threshold << TestLog::EndMessage;

	for (int y = 0; y < surface.getHeight(); y++)
	{
		for (int x = 0; x < surface.getWidth(); x++)
		{
			const tcu::RGBA		resultColor		= surface.getPixel(x, y);
			const bool			isOk			= compareThreshold(resultColor, expectedColor, threshold);

			if (!isOk)
			{
				if (numFailedPixels < maxPrints)
					log << TestLog::Message << "ERROR: Got " << resultColor << " at (" << x << ", " << y << ")!" << TestLog::EndMessage;
				else if (numFailedPixels == maxPrints)
					log << TestLog::Message << "..." << TestLog::EndMessage;

				numFailedPixels += 1;
			}
		}
	}

	if (numFailedPixels > 0)
	{
		log << TestLog::Message << "Found " << numFailedPixels << " invalid pixels, comparison FAILED!" << TestLog::EndMessage;
		log << TestLog::Image("ResultImage", "Result Image", surface);
		return false;
	}
	else
	{
		log << TestLog::Message << "Image comparison passed." << TestLog::EndMessage;
		return true;
	}
}

class FragDataIndexingCase : public TestCase
{
public:
	FragDataIndexingCase (Context& context, const char* name, const char* description, IndexExprType indexExprType)
		: TestCase			(context, name, description)
		, m_indexExprType	(indexExprType)
	{
	}

	static glu::ProgramSources genSources (const IndexExprType indexExprType)
	{
		const char* const	fragIndexExpr	= indexExprType == INDEX_EXPR_STATIC	? "0"				:
											  indexExprType == INDEX_EXPR_UNIFORM	? "u_index"			:
											  indexExprType == INDEX_EXPR_DYNAMIC	? "int(v_index)"	: DE_NULL;
		glu::ProgramSources	sources;

		DE_ASSERT(fragIndexExpr);

		sources << glu::VertexSource(
			"attribute highp vec4 a_position;\n"
			"attribute highp float a_index;\n"
			"attribute highp vec4 a_color;\n"
			"varying mediump float v_index;\n"
			"varying mediump vec4 v_color;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"	v_color = a_color;\n"
			"	v_index = a_index;\n"
			"}\n");

		sources << glu::FragmentSource(string(
			"varying mediump vec4 v_color;\n"
			"varying mediump float v_index;\n"
			"uniform mediump int u_index;\n"
			"void main (void)\n"
			"{\n"
			"	gl_FragData[") + fragIndexExpr + "] = v_color;\n"
			"}\n");

		return sources;
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&		renderCtx		= m_context.getRenderContext();
		const glw::Functions&			gl				= renderCtx.getFunctions();
		const glu::ShaderProgram		program			(renderCtx, genSources(m_indexExprType));
		const int						viewportW		= de::min(renderCtx.getRenderTarget().getWidth(), 128);
		const int						viewportH		= de::min(renderCtx.getRenderTarget().getHeight(), 128);

		const float positions[] =
		{
			-1.0f, -1.0f,
			+1.0f, -1.0f,
			-1.0f, +1.0f,
			+1.0f, +1.0f
		};
		const float colors[] =
		{
			0.0f, 1.0f, 0.0f, 1.0f,
			0.0f, 1.0f, 0.0f, 1.0f,
			0.0f, 1.0f, 0.0f, 1.0f,
			0.0f, 1.0f, 0.0f, 1.0f
		};
		const float		indexValues[]	= { 0.0f, 0.0f, 0.0f, 0.0f };
		const deUint8	indices[]		= { 0, 1, 2, 2, 1, 3 };

		const glu::VertexArrayBinding vertexArrays[] =
		{
			glu::va::Float("a_position",	2, 4, 0, &positions[0]),
			glu::va::Float("a_color",		4, 4, 0, &colors[0]),
			glu::va::Float("a_index",		1, 4, 0, &indexValues[0])
		};

		m_testCtx.getLog() << program;

		if (!program.isOk())
		{
			if (m_indexExprType == INDEX_EXPR_STATIC)
				TCU_FAIL("Compile failed");
			else
				throw tcu::NotSupportedError("Dynamic indexing of gl_FragData[] not supported");
		}

		gl.clearColor	(1.0f, 0.0f, 0.0f, 1.0f);
		gl.clear		(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

		gl.viewport		(0, 0, viewportW, viewportH);
		gl.useProgram	(program.getProgram());
		gl.uniform1i	(gl.getUniformLocation(program.getProgram(), "u_index"), 0);

		glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
		GLU_EXPECT_NO_ERROR(gl.getError(), "Rendering failed");

		{
			tcu::Surface		result		(viewportW, viewportH);
			const tcu::RGBA		threshold	= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
			bool				isOk;

			glu::readPixels(renderCtx, 0, 0, result.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");

			isOk = compareSingleColor(m_testCtx.getLog(), result, tcu::RGBA::green, threshold);

			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
									isOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}

private:
	const IndexExprType m_indexExprType;
};

class FragDataDrawBuffersCase : public TestCase
{
public:
	FragDataDrawBuffersCase (Context& context)
		: TestCase(context, "draw_buffers", "gl_FragData[] and glDrawBuffers() interaction")
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&		renderCtx		= m_context.getRenderContext();
		const glu::ShaderProgram		program			(renderCtx, glu::ProgramSources()
															<< glu::VertexSource(
																"attribute highp vec4 a_position;\n"
																"attribute highp vec4 a_color;\n"
																"varying mediump vec4 v_color;\n"
																"void main (void)\n"
																"{\n"
																"	gl_Position = a_position;\n"
																"	v_color = a_color;\n"
																"}\n")
															<< glu::FragmentSource(
																"varying mediump vec4 v_color;\n"
																"uniform mediump int u_index;\n"
																"void main (void)\n"
																"{\n"
																"	gl_FragData[u_index] = v_color;\n"
																"}\n"));
		const glw::Functions&			gl				= renderCtx.getFunctions();
		const int						width			= 128;
		const int						height			= 128;
		const int						indexLoc		= program.isOk() ? gl.getUniformLocation(program.getProgram(), "u_index") : -1;
		const glu::Framebuffer			fbo				(renderCtx);
		const glu::Renderbuffer			colorBuf0		(renderCtx);
		const glu::Renderbuffer			colorBuf1		(renderCtx);

		const float positions[] =
		{
			-1.0f, -1.0f,
			+1.0f, -1.0f,
			-1.0f, +1.0f,
			+1.0f, +1.0f
		};
		const float colors[] =
		{
			0.0f, 1.0f, 0.0f, 1.0f,
			0.0f, 1.0f, 0.0f, 1.0f,
			0.0f, 1.0f, 0.0f, 1.0f,
			0.0f, 1.0f, 0.0f, 1.0f
		};
		const deUint8	indices[]		= { 0, 1, 2, 2, 1, 3 };

		const glu::VertexArrayBinding vertexArrays[] =
		{
			glu::va::Float("a_position",	2, 4, 0, &positions[0]),
			glu::va::Float("a_color",		4, 4, 0, &colors[0])
		};

		m_testCtx.getLog() << program;

		if (!program.isOk())
			throw tcu::NotSupportedError("Dynamic indexing of gl_FragData[] not supported");

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		for (int ndx = 0; ndx < 2; ndx++)
		{
			const deUint32	rbo	= ndx == 0 ? *colorBuf0 : *colorBuf1;

			gl.bindRenderbuffer(GL_RENDERBUFFER, rbo);
			gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
			gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+ndx, GL_RENDERBUFFER, rbo);
		}
		TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

		{
			const deUint32 drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
			gl.drawBuffers(DE_LENGTH_OF_ARRAY(drawBuffers), &drawBuffers[0]);
		}

		gl.clearBufferfv(GL_COLOR, 0, tcu::RGBA::red.toVec().getPtr());
		gl.clearBufferfv(GL_COLOR, 1, tcu::RGBA::red.toVec().getPtr());

		gl.viewport		(0, 0, width, height);
		gl.useProgram	(program.getProgram());

		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

		m_testCtx.getLog() << TestLog::Message << "Drawing to attachments 0 and 1, expecting only attachment 0 to change." << TestLog::EndMessage;

		for (int ndx = 0; ndx < 2; ndx++)
		{
			gl.uniform1i(indexLoc, ndx);
			glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
		}
		GLU_EXPECT_NO_ERROR(gl.getError(), "Rendering failed");

		{
			tcu::Surface		result		(width, height);
			const tcu::RGBA		threshold	= renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1,1,1,1);
			bool				allOk		= true;

			for (int ndx = 0; ndx < 2; ndx++)
			{
				m_testCtx.getLog() << TestLog::Message << "Verifying attachment " << ndx << "..." << TestLog::EndMessage;

				gl.readBuffer(GL_COLOR_ATTACHMENT0+ndx);
				glu::readPixels(renderCtx, 0, 0, result.getAccess());
				GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");

				if (!compareSingleColor(m_testCtx.getLog(), result, ndx == 0 ? tcu::RGBA::green : tcu::RGBA::red, threshold))
					allOk = false;
			}

			m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
									allOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}
};

} // anonymous

ShaderFragDataTests::ShaderFragDataTests (Context& context)
	: TestCaseGroup(context, "fragdata", "gl_FragData[] Tests")
{
}

ShaderFragDataTests::~ShaderFragDataTests (void)
{
}

void ShaderFragDataTests::init (void)
{
	addChild(new FragDataIndexingCase		(m_context, "valid_static_index",	"Valid gl_FragData[] assignment using static index",	INDEX_EXPR_STATIC));
	addChild(new FragDataIndexingCase		(m_context, "valid_uniform_index",	"Valid gl_FragData[] assignment using uniform index",	INDEX_EXPR_UNIFORM));
	addChild(new FragDataIndexingCase		(m_context, "valid_dynamic_index",	"Valid gl_FragData[] assignment using dynamic index",	INDEX_EXPR_DYNAMIC));
	addChild(new FragDataDrawBuffersCase	(m_context));

	// Negative cases.
	{
		gls::ShaderLibrary library(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
		std::vector<tcu::TestNode*> negativeCases = library.loadShaderFile("shaders/fragdata.test");

		for (std::vector<tcu::TestNode*>::iterator i = negativeCases.begin(); i != negativeCases.end(); i++)
			addChild(*i);
	}
}

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