/*-------------------------------------------------------------------------
 * 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 Buffer copying tests.
 *//*--------------------------------------------------------------------*/

#include "es3fBufferCopyTests.hpp"
#include "glsBufferTestUtil.hpp"
#include "tcuTestLog.hpp"
#include "deMemory.h"
#include "deString.h"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <algorithm>

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

namespace deqp
{
namespace gles3
{
namespace Functional
{

using namespace gls::BufferTestUtil;

class BasicBufferCopyCase : public BufferCase
{
public:
	BasicBufferCopyCase (Context&		context,
						 const char*	name,
						 const char*	desc,
						 deUint32		srcTarget,
						 int			srcSize,
						 deUint32		srcHint,
						 deUint32		dstTarget,
						 int			dstSize,
						 deUint32		dstHint,
						 int			copySrcOffset,
						 int			copyDstOffset,
						 int			copySize,
						 VerifyType		verifyType)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_srcTarget		(srcTarget)
		, m_srcSize			(srcSize)
		, m_srcHint			(srcHint)
		, m_dstTarget		(dstTarget)
		, m_dstSize			(dstSize)
		, m_dstHint			(dstHint)
		, m_copySrcOffset	(copySrcOffset)
		, m_copyDstOffset	(copyDstOffset)
		, m_copySize		(copySize)
		, m_verifyType		(verifyType)
	{
		DE_ASSERT(de::inBounds(m_copySrcOffset, 0, m_srcSize) && de::inRange(m_copySrcOffset+m_copySize, m_copySrcOffset, m_srcSize));
		DE_ASSERT(de::inBounds(m_copyDstOffset, 0, m_dstSize) && de::inRange(m_copyDstOffset+m_copySize, m_copyDstOffset, m_dstSize));
	}

	IterateResult iterate (void)
	{
		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verifyType);
		ReferenceBuffer	srcRef;
		ReferenceBuffer	dstRef;
		deUint32		srcBuf		= 0;
		deUint32		dstBuf		= 0;
		deUint32		srcSeed		= deStringHash(getName()) ^ 0xabcd;
		deUint32		dstSeed		= deStringHash(getName()) ^ 0xef01;
		bool			isOk		= true;

		srcRef.setSize(m_srcSize);
		fillWithRandomBytes(srcRef.getPtr(), m_srcSize, srcSeed);

		dstRef.setSize(m_dstSize);
		fillWithRandomBytes(dstRef.getPtr(), m_dstSize, dstSeed);

		// Create source buffer and fill with data.
		srcBuf = genBuffer();
		glBindBuffer(m_srcTarget, srcBuf);
		glBufferData(m_srcTarget, m_srcSize, srcRef.getPtr(), m_srcHint);
		GLU_CHECK_MSG("glBufferData");

		// Create destination buffer and fill with data.
		dstBuf = genBuffer();
		glBindBuffer(m_dstTarget, dstBuf);
		glBufferData(m_dstTarget, m_dstSize, dstRef.getPtr(), m_dstHint);
		GLU_CHECK_MSG("glBufferData");

		// Verify both buffers before executing copy.
		isOk = verifier.verify(srcBuf, srcRef.getPtr(), 0, m_srcSize, m_srcTarget) && isOk;
		isOk = verifier.verify(dstBuf, dstRef.getPtr(), 0, m_dstSize, m_dstTarget) && isOk;

		// Execute copy.
		deMemcpy(dstRef.getPtr()+m_copyDstOffset, srcRef.getPtr()+m_copySrcOffset, m_copySize);

		glBindBuffer(m_srcTarget, srcBuf);
		glBindBuffer(m_dstTarget, dstBuf);
		glCopyBufferSubData(m_srcTarget, m_dstTarget, m_copySrcOffset, m_copyDstOffset, m_copySize);
		GLU_CHECK_MSG("glCopyBufferSubData");

		// Verify both buffers after copy.
		isOk = verifier.verify(srcBuf, srcRef.getPtr(), 0, m_srcSize, m_srcTarget) && isOk;
		isOk = verifier.verify(dstBuf, dstRef.getPtr(), 0, m_dstSize, m_dstTarget) && isOk;

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

private:
	deUint32	m_srcTarget;
	int			m_srcSize;
	deUint32	m_srcHint;

	deUint32	m_dstTarget;
	int			m_dstSize;
	deUint32	m_dstHint;

	int			m_copySrcOffset;
	int			m_copyDstOffset;
	int			m_copySize;

	VerifyType	m_verifyType;
};

// Case B: same buffer, take range as parameter

class SingleBufferCopyCase : public BufferCase
{
public:
	SingleBufferCopyCase (Context&		context,
						  const char*	name,
						  const char*	desc,
						  deUint32		srcTarget,
						  deUint32		dstTarget,
						  deUint32		hint,
						  VerifyType	verifyType)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_srcTarget		(srcTarget)
		, m_dstTarget		(dstTarget)
		, m_hint			(hint)
		, m_verifyType		(verifyType)
	{
	}

	IterateResult iterate (void)
	{
		const int		size		= 1000;
		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verifyType);
		ReferenceBuffer	ref;
		deUint32		buf			= 0;
		deUint32		baseSeed	= deStringHash(getName());
		bool			isOk		= true;

		ref.setSize(size);

		// Create buffer.
		buf = genBuffer();
		glBindBuffer(m_srcTarget, buf);

		static const struct
		{
			int				srcOffset;
			int				dstOffset;
			int				copySize;
		} copyRanges[] =
		{
			{ 57,		701,	101 },	// Non-adjecent, from low to high.
			{ 640,		101,	101 },	// Non-adjecent, from high to low.
			{ 0,		500,	500 },	// Lower half to upper half.
			{ 500,		0,		500 }	// Upper half to lower half.
		};

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(copyRanges) && isOk; ndx++)
		{
			int	srcOffset	= copyRanges[ndx].srcOffset;
			int	dstOffset	= copyRanges[ndx].dstOffset;
			int	copySize	= copyRanges[ndx].copySize;

			fillWithRandomBytes(ref.getPtr(), size, baseSeed ^ deInt32Hash(ndx));

			// Fill with data.
			glBindBuffer(m_srcTarget, buf);
			glBufferData(m_srcTarget, size, ref.getPtr(), m_hint);
			GLU_CHECK_MSG("glBufferData");

			// Execute copy.
			deMemcpy(ref.getPtr()+dstOffset, ref.getPtr()+srcOffset, copySize);

			glBindBuffer(m_dstTarget, buf);
			glCopyBufferSubData(m_srcTarget, m_dstTarget, srcOffset, dstOffset, copySize);
			GLU_CHECK_MSG("glCopyBufferSubData");

			// Verify buffer after copy.
			isOk = verifier.verify(buf, ref.getPtr(), 0, size, m_dstTarget) && isOk;
		}

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

private:
	deUint32	m_srcTarget;
	deUint32	m_dstTarget;
	deUint32	m_hint;

	VerifyType	m_verifyType;
};

BufferCopyTests::BufferCopyTests (Context& context)
	: TestCaseGroup(context, "copy", "Buffer copy tests")
{
}

BufferCopyTests::~BufferCopyTests (void)
{
}

void BufferCopyTests::init (void)
{
	static const deUint32 bufferTargets[] =
	{
		GL_ARRAY_BUFFER,
		GL_COPY_READ_BUFFER,
		GL_COPY_WRITE_BUFFER,
		GL_ELEMENT_ARRAY_BUFFER,
		GL_PIXEL_PACK_BUFFER,
		GL_PIXEL_UNPACK_BUFFER,
		GL_TRANSFORM_FEEDBACK_BUFFER,
		GL_UNIFORM_BUFFER
	};

	// .basic
	{
		tcu::TestCaseGroup* basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic buffer copy cases");
		addChild(basicGroup);

		for (int srcTargetNdx = 0; srcTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); srcTargetNdx++)
		{
			for (int dstTargetNdx = 0; dstTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); dstTargetNdx++)
			{
				if (srcTargetNdx == dstTargetNdx)
					continue;

				deUint32		srcTarget		= bufferTargets[srcTargetNdx];
				deUint32		dstTarget		= bufferTargets[dstTargetNdx];
				const int		size			= 1017;
				const deUint32	hint			= GL_STATIC_DRAW;
				VerifyType		verify			= VERIFY_AS_VERTEX_ARRAY;
				string			name			= string(getBufferTargetName(srcTarget)) + "_" + getBufferTargetName(dstTarget);

				basicGroup->addChild(new BasicBufferCopyCase(m_context, name.c_str(), "", srcTarget, size, hint, dstTarget, size, hint, 0, 0, size, verify));
			}
		}
	}

	// .subrange
	{
		tcu::TestCaseGroup* subrangeGroup = new tcu::TestCaseGroup(m_testCtx, "subrange", "Buffer subrange copy tests");
		addChild(subrangeGroup);

		static const struct
		{
			const char*		name;
			int				srcSize;
			int				dstSize;
			int				srcOffset;
			int				dstOffset;
			int				copySize;
		} cases[] =
		{
			//						srcSize		dstSize		srcOffs		dstOffs		copySize
			{ "middle",				1000,		1000,		250,		250,		500 },
			{ "small_to_large",		100,		1000,		0,			409,		100 },
			{ "large_to_small",		1000,		100,		409,		0,			100 },
			{ "low_to_high_1",		1000,		1000,		0,			500,		500 },
			{ "low_to_high_2",		997,		1027,		0,			701,		111 },
			{ "high_to_low_1",		1000,		1000,		500,		0,			500 },
			{ "high_to_low_2",		1027,		997,		701,		17,			111 }
		};

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
		{
			deUint32		srcTarget		= GL_COPY_READ_BUFFER;
			deUint32		dstTarget		= GL_COPY_WRITE_BUFFER;
			deUint32		hint			= GL_STATIC_DRAW;
			VerifyType		verify			= VERIFY_AS_VERTEX_ARRAY;

			subrangeGroup->addChild(new BasicBufferCopyCase(m_context, cases[ndx].name, "",
															srcTarget, cases[ndx].srcSize, hint,
															dstTarget, cases[ndx].dstSize, hint,
															cases[ndx].srcOffset, cases[ndx].dstOffset, cases[ndx].copySize,
															verify));
		}
	}

	// .single_buffer
	{
		tcu::TestCaseGroup* singleBufGroup = new tcu::TestCaseGroup(m_testCtx, "single_buffer", "Copies within single buffer");
		addChild(singleBufGroup);

		for (int srcTargetNdx = 0; srcTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); srcTargetNdx++)
		{
			for (int dstTargetNdx = 0; dstTargetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); dstTargetNdx++)
			{
				if (srcTargetNdx == dstTargetNdx)
					continue;

				deUint32		srcTarget		= bufferTargets[srcTargetNdx];
				deUint32		dstTarget		= bufferTargets[dstTargetNdx];
				const deUint32	hint			= GL_STATIC_DRAW;
				VerifyType		verify			= VERIFY_AS_VERTEX_ARRAY;
				string			name			= string(getBufferTargetName(srcTarget)) + "_" + getBufferTargetName(dstTarget);

				singleBufGroup->addChild(new SingleBufferCopyCase(m_context, name.c_str(), "", srcTarget, dstTarget, hint, verify));
			}
		}
	}
}

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