/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) 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 Single-program test case wrapper for ShaderPerformanceMeasurer.
 *//*--------------------------------------------------------------------*/

#include "glsShaderPerformanceCase.hpp"
#include "tcuRenderTarget.hpp"
#include "deStringUtil.hpp"
#include "deMath.h"

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

using tcu::Vec4;
using tcu::TestLog;
using namespace glw; // GL types

namespace deqp
{
namespace gls
{

ShaderPerformanceCase::ShaderPerformanceCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, PerfCaseType caseType)
	: tcu::TestCase		(testCtx, tcu::NODETYPE_PERFORMANCE, name, description)
	, m_renderCtx		(renderCtx)
	, m_caseType		(caseType)
	, m_program			(DE_NULL)
	, m_measurer		(renderCtx, caseType)
{
}

ShaderPerformanceCase::~ShaderPerformanceCase (void)
{
	ShaderPerformanceCase::deinit();
}

void ShaderPerformanceCase::setGridSize (int gridW, int gridH)
{
	m_measurer.setGridSize(gridW, gridH);
}

void ShaderPerformanceCase::setViewportSize (int width, int height)
{
	m_measurer.setViewportSize(width, height);
}

void ShaderPerformanceCase::setVertexFragmentRatio (float fragmentsPerVertices)
{
	const float	eps			= 0.01f;
	int			gridW		= 255;
	int			gridH		= 255;
	int			viewportW	= m_renderCtx.getRenderTarget().getWidth();
	int			viewportH	= m_renderCtx.getRenderTarget().getHeight();

	for (int i = 0; i < 10; i++)
	{
		int		numVert	= (gridW+1)*(gridH+1);
		int		numFrag	= viewportW*viewportH;
		float	ratio	= (float)numFrag / (float)numVert;

		if (de::abs(ratio - fragmentsPerVertices) < eps)
			break;
		else if (ratio < fragmentsPerVertices)
		{
			// Not enough fragments.
			numVert = deRoundFloatToInt32((float)numFrag / fragmentsPerVertices);

			while ((gridW+1)*(gridH+1) > numVert)
			{
				if (gridW > gridH)
					gridW -= 1;
				else
					gridH -= 1;
			}
		}
		else
		{
			// Not enough vertices.
			numFrag = deRoundFloatToInt32((float)numVert * fragmentsPerVertices);

			while (viewportW*viewportH > numFrag)
			{
				if (viewportW > viewportH)
					viewportW -= 1;
				else
					viewportH -= 1;
			}
		}
	}

	float finalRatio = (float)(viewportW*viewportH) / (float)((gridW+1)*(gridH+1));
	m_testCtx.getLog() << TestLog::Message << "Requested fragment/vertex-ratio: " << de::floatToString(fragmentsPerVertices, 2) << "\n"
										   << "Computed fragment/vertex-ratio: " << de::floatToString(finalRatio, 2)
					   << TestLog::EndMessage;

	setGridSize(gridW, gridH);
	setViewportSize(viewportW, viewportH);
}

static void logRenderTargetInfo (TestLog& log, const tcu::RenderTarget& renderTarget)
{
	log << TestLog::Section("RenderTarget", "Render target")
		<< TestLog::Message << "size: " << renderTarget.getWidth() << "x" << renderTarget.getHeight() << TestLog::EndMessage
		<< TestLog::Message << "bits:"
							<< " R" << renderTarget.getPixelFormat().redBits
							<< " G" << renderTarget.getPixelFormat().greenBits
							<< " B" << renderTarget.getPixelFormat().blueBits
							<< " A" << renderTarget.getPixelFormat().alphaBits
							<< " D" << renderTarget.getDepthBits()
							<< " S" << renderTarget.getStencilBits()
							<< TestLog::EndMessage;

	if (renderTarget.getNumSamples() != 0)
		log << TestLog::Message << renderTarget.getNumSamples() << "x MSAA" << TestLog::EndMessage;
	else
		log << TestLog::Message << "No MSAA" << TestLog::EndMessage;

	log << TestLog::EndSection;
}

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

	m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(m_vertShaderSource, m_fragShaderSource));

	if (m_program->isOk())
	{
		const int initialCallCount = m_initialCalibration ? m_initialCalibration->initialNumCalls : 1;
		logRenderTargetInfo(log, m_renderCtx.getRenderTarget());
		m_measurer.init(m_program->getProgram(), m_attributes, initialCallCount);
		m_measurer.logParameters(log);
		log << *m_program;
	}
	else
	{
		log << *m_program;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
		return; // Skip rest of init.
	}

	setupProgram(m_program->getProgram());
	setupRenderState();
}

void ShaderPerformanceCase::deinit (void)
{
	delete m_program;
	m_program = DE_NULL;

	m_measurer.deinit();
}

void ShaderPerformanceCase::setupProgram (deUint32 program)
{
	DE_UNREF(program);
}

void ShaderPerformanceCase::setupRenderState (void)
{
}

ShaderPerformanceCase::IterateResult ShaderPerformanceCase::iterate (void)
{
	DE_ASSERT(m_program);

	if (!m_program->isOk()) // This happens when compilation failed in init().
		return STOP;

	m_measurer.iterate();

	if (m_measurer.isFinished())
	{
		m_measurer.logMeasurementInfo(m_testCtx.getLog());

		if (m_initialCalibration)
			m_initialCalibration->initialNumCalls = de::max(1, m_measurer.getFinalCallCount());

		const ShaderPerformanceMeasurer::Result result = m_measurer.getResult();
		reportResult(result.megaVertPerSec, result.megaFragPerSec);
		return STOP;
	}
	else
		return CONTINUE;
}

void ShaderPerformanceCase::reportResult (float mvertPerSecond, float mfragPerSecond)
{
	float result = 0.0f;
	switch (m_caseType)
	{
		case CASETYPE_VERTEX:	result = mvertPerSecond;	break;
		case CASETYPE_FRAGMENT:	result = mfragPerSecond;	break;
		case CASETYPE_BALANCED:	result = mfragPerSecond;	break;
		default:
			DE_ASSERT(false);
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString(result, 2).c_str());
}

ShaderPerformanceCaseGroup::ShaderPerformanceCaseGroup (tcu::TestContext& testCtx, const char* name, const char* description)
	: TestCaseGroup					(testCtx, name, description)
	, m_initialCalibrationStorage	(new ShaderPerformanceCase::InitialCalibration)
{
}

void ShaderPerformanceCaseGroup::addChild (ShaderPerformanceCase* perfCase)
{
	perfCase->setCalibrationInitialParamStorage(m_initialCalibrationStorage);
	TestCaseGroup::addChild(perfCase);
}

} // gls
} // deqp