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

#include "es2pTextureCases.hpp"
#include "glsShaderPerformanceCase.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "gluTexture.hpp"
#include "gluStrUtil.hpp"

#include "deStringUtil.hpp"

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

namespace deqp
{
namespace gles2
{
namespace Performance
{

using namespace gls;
using namespace glw; // GL types
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec4;
using std::string;
using std::vector;
using tcu::TestLog;

Texture2DRenderCase::Texture2DRenderCase (Context&			context,
										  const char*		name,
										  const char*		description,
										  deUint32			format,
										  deUint32			dataType,
										  deUint32			wrapS,
										  deUint32			wrapT,
										  deUint32			minFilter,
										  deUint32			magFilter,
										  const tcu::Mat3&	coordTransform,
										  int				numTextures,
										  bool				powerOfTwo)
	: ShaderPerformanceCase	(context.getTestContext(), context.getRenderContext(), name, description, CASETYPE_FRAGMENT)
	, m_format				(format)
	, m_dataType			(dataType)
	, m_wrapS				(wrapS)
	, m_wrapT				(wrapT)
	, m_minFilter			(minFilter)
	, m_magFilter			(magFilter)
	, m_coordTransform		(coordTransform)
	, m_numTextures			(numTextures)
	, m_powerOfTwo			(powerOfTwo)
{
}

Texture2DRenderCase::~Texture2DRenderCase (void)
{
	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
		delete *i;
	m_textures.clear();
}

static inline int roundDownToPowerOfTwo (int val)
{
	DE_ASSERT(val >= 0);
	int l0 = deClz32(val);
	return val & ~((1<<(31-l0))-1);
}

void Texture2DRenderCase::init (void)
{
	TestLog& log = m_testCtx.getLog();

	int width	= m_renderCtx.getRenderTarget().getWidth();
	int height	= m_renderCtx.getRenderTarget().getHeight();

	if (m_powerOfTwo)
	{
		width	= roundDownToPowerOfTwo(width);
		height	= roundDownToPowerOfTwo(height);
	}

	bool mipmaps = m_minFilter == GL_NEAREST_MIPMAP_NEAREST ||
				   m_minFilter == GL_NEAREST_MIPMAP_LINEAR	||
				   m_minFilter == GL_LINEAR_MIPMAP_NEAREST	||
				   m_minFilter == GL_LINEAR_MIPMAP_LINEAR;

	DE_ASSERT(m_powerOfTwo || (!mipmaps && m_wrapS == GL_CLAMP_TO_EDGE && m_wrapT == GL_CLAMP_TO_EDGE));

	Vec2 p00 = (m_coordTransform * Vec3(0.0f, 0.0f, 1.0f)).swizzle(0,1);
	Vec2 p10 = (m_coordTransform * Vec3(1.0f, 0.0f, 1.0f)).swizzle(0,1);
	Vec2 p01 = (m_coordTransform * Vec3(0.0f, 1.0f, 1.0f)).swizzle(0,1);
	Vec2 p11 = (m_coordTransform * Vec3(1.0f, 1.0f, 1.0f)).swizzle(0,1);

	m_attributes.push_back(AttribSpec("a_coords", Vec4(p00.x(), p00.y(), 0.0f, 0.0f),
												  Vec4(p10.x(), p10.y(), 0.0f, 0.0f),
												  Vec4(p01.x(), p01.y(), 0.0f, 0.0f),
												  Vec4(p11.x(), p11.y(), 0.0f, 0.0f)));

	log << TestLog::Message << "Size: " << width << "x" << height << TestLog::EndMessage;
	log << TestLog::Message << "Format: " <<glu::getPixelFormatName(m_format) << " " << glu::getTypeName(m_dataType) << TestLog::EndMessage;
	log << TestLog::Message << "Coords: " << p00 << ", " << p10 << ", " << p01 << ", " << p11 << TestLog::EndMessage;
	log << TestLog::Message << "Wrap: " << glu::getTextureWrapModeStr(m_wrapS) << " / " << glu::getTextureWrapModeStr(m_wrapT) << TestLog::EndMessage;
	log << TestLog::Message << "Filter: " << glu::getTextureFilterStr(m_minFilter) << " / " << glu::getTextureFilterStr(m_magFilter) << TestLog::EndMessage;
	log << TestLog::Message << "Mipmaps: " << (mipmaps ? "true" : "false") << TestLog::EndMessage;
	log << TestLog::Message << "Using additive blending." << TestLog::EndMessage;

	// Use same viewport size as texture size.
	setViewportSize(width, height);

	m_vertShaderSource =
		"attribute highp vec4 a_position;\n"
		"attribute mediump vec2 a_coords;\n"
		"varying mediump vec2 v_coords;\n"
		"void main (void)\n"
		"{\n"
		"	gl_Position = a_position;\n"
		"	v_coords = a_coords;\n"
		"}\n";

	std::ostringstream fragSrc;
	fragSrc << "varying mediump vec2 v_coords;\n";

	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
		fragSrc << "uniform sampler2D u_sampler" << texNdx << ";\n";

	fragSrc << "void main (void)\n"
			<< "{\n";

	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
		fragSrc << "\t" << (texNdx == 0 ? "lowp vec4 r = " : "r += ") << "texture2D(u_sampler" << texNdx << ", v_coords);\n";

	fragSrc << "	gl_FragColor = r;\n"
			<< "}\n";

	m_fragShaderSource = fragSrc.str();

	m_textures.reserve(m_numTextures);
	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
	{
		static const IVec4 swizzles[] = { IVec4(0,1,2,3), IVec4(1,2,3,0), IVec4(2,3,0,1), IVec4(3,0,1,2),
										  IVec4(3,2,1,0), IVec4(2,1,0,3), IVec4(1,0,3,2), IVec4(0,3,2,1) };
		const IVec4& sw = swizzles[texNdx % DE_LENGTH_OF_ARRAY(swizzles)];

		glu::Texture2D* texture = new glu::Texture2D(m_renderCtx, m_format, m_dataType, width, height);
		m_textures.push_back(texture);

		// Fill levels.
		int numLevels = mipmaps ? texture->getRefTexture().getNumLevels() : 1;
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			texture->getRefTexture().allocLevel(levelNdx);
			tcu::fillWithComponentGradients(texture->getRefTexture().getLevel(levelNdx),
											Vec4(0.0f, 0.0f, 0.0f, 0.0f).swizzle(sw[0], sw[1], sw[2], sw[3]),
											Vec4(1.0f, 1.0f, 1.0f, 1.0f).swizzle(sw[0], sw[1], sw[2], sw[3]));
		}

		texture->upload();
	}

	ShaderPerformanceCase::init();
}

void Texture2DRenderCase::deinit (void)
{
	for (vector<glu::Texture2D*>::iterator i = m_textures.begin(); i != m_textures.end(); i++)
		delete *i;
	m_textures.clear();

	ShaderPerformanceCase::deinit();
}

void Texture2DRenderCase::setupProgram (deUint32 program)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();
	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
	{
		int samplerLoc = gl.getUniformLocation(program, (string("u_sampler") + de::toString(texNdx)).c_str());
		gl.uniform1i(samplerLoc, texNdx);
	}
}

void Texture2DRenderCase::setupRenderState (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	// Setup additive blending.
	gl.enable(GL_BLEND);
	gl.blendFunc(GL_ONE, GL_ONE);
	gl.blendEquation(GL_FUNC_ADD);

	// Setup textures.
	for (int texNdx = 0; texNdx < m_numTextures; texNdx++)
	{
		gl.activeTexture(GL_TEXTURE0 + texNdx);
		gl.bindTexture(GL_TEXTURE_2D, m_textures[texNdx]->getGLTexture());
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
	}
}


} // Performance
} // gles2
} // deqp