/*-------------------------------------------------------------------------
 * OpenGL Conformance Test Suite
 * -----------------------------
 *
 * Copyright (c) 2015-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
 */ /*-------------------------------------------------------------------*/

/**
 * \file  gl4cSyncTests.cpp
 * \brief Declares test classes for synchronization functionality.
 */ /*-------------------------------------------------------------------*/

#include "gl4cSyncTests.hpp"

#include "deSharedPtr.hpp"

#include "gluContextInfo.hpp"
#include "gluDefs.hpp"
#include "gluPixelTransfer.hpp"
#include "gluStrUtil.hpp"

#include "tcuFuzzyImageCompare.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"

#include "glw.h"
#include "glwFunctions.hpp"

/* Timeout of the test in nanoseconds. */
#define TEST_SYNC_WAIT_TIMEOUT 16000000000

namespace gl4cts
{
namespace Sync
{
/****************************************** Tests Group ***********************************************/

/** @brief Sync Tests Group constructor.
 *
 *  @param [in] context     OpenGL context.
 */
Tests::Tests(deqp::Context& context) : TestCaseGroup(context, "sync", "Sync Tests Suite")
{
}

/** @brief Sync Tests initializer. */
void Tests::init()
{
	addChild(new Sync::FlushCommandsTest(m_context));
}

/*************************************** Flush Commands Test *******************************************/

/** @brief Sync Flush Commands Test constructor.
 *
 *  @param [in] context     OpenGL context.
 */
FlushCommandsTest::FlushCommandsTest(deqp::Context& context)
	: deqp::TestCase(context, "flush_commands", "Sync Flush Commands Test")
{
	/* Intentionally left blank. */
}

/** @brief Iterate Sync Flush Commands Test cases.
 *
 *  @return Iteration result.
 */
tcu::TestNode::IterateResult FlushCommandsTest::iterate()
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	/* Get context setup. */
	bool is_at_least_gl_45 = (glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5)));
	bool is_arb_direct_state_access = m_context.getContextInfo().isExtensionSupported("GL_ARB_direct_state_access");

	if ((!is_at_least_gl_45) && (!is_arb_direct_state_access))
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not Supported");

		return STOP;
	}

	/* Running tests. */
	bool is_ok		= false;
	bool is_error   = false;
	bool is_timeout = false;

	/* Test constants. */
	static const glw::GLuint reference[2]   = { 3, 1415927 };
	static const glw::GLuint reference_size = sizeof(reference);

	/* Test objects. */
	glw::GLuint buffer_src = 0;
	glw::GLuint buffer_dst = 0;
	glw::GLsync sync	   = 0;
	glw::GLenum error	  = GL_NO_ERROR;

	try
	{
		/* Prepare buffers. */
		gl.createBuffers(1, &buffer_src);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glCreateBuffers have failed");
		gl.namedBufferData(buffer_src, reference_size, reference, GL_STATIC_COPY);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glNamedBufferData have failed");

		gl.createBuffers(1, &buffer_dst);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glCreateBuffers have failed");
		gl.namedBufferStorage(buffer_dst, reference_size, NULL,
							  GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glNamedBufferStorage have failed");

		/* Map perisistently buffer range. */
		glw::GLuint* data_dst = (glw::GLuint*)gl.mapNamedBufferRange(
			buffer_dst, 0, reference_size, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapNamedBufferRange have failed");

		/* Copy data from source to destination buffer */
		gl.copyNamedBufferSubData(buffer_src, buffer_dst, 0, 0, reference_size);

		if (GL_NO_ERROR != (error = gl.getError()))
		{
			gl.unmapNamedBuffer(buffer_dst);

			GLU_EXPECT_NO_ERROR(error, "glCopyNamedBufferSubData have failed");
		}

		/* Create fence sync object. */
		sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

		if (GL_NO_ERROR == (error = gl.getError()))
		{
			/* Wait until done. */
			glw::GLenum wait_result = gl.clientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, TEST_SYNC_WAIT_TIMEOUT);

			/* Check for error. */
			if (GL_NO_ERROR == (error = gl.getError()))
			{
				/* Check for timeout. */
				if (GL_TIMEOUT_EXPIRED == wait_result)
				{
					m_context.getTestContext().getLog()
						<< tcu::TestLog::Message
						<< "ClientWaitSync with SYNC_FLUSH_COMMANDS_BIT flag has returned TIMEOUT_EXPIRED after "
						<< TEST_SYNC_WAIT_TIMEOUT << " nanoseconds. Potentially test may not be done in finite time "
													 "which is expected (OpenGL 4.5 Core Profile, Chapter 4.1.2)."
						<< " However this cannot be proven in finite time. Test timeouts." << tcu::TestLog::EndMessage;

					is_timeout = true;
				} /* Check for proper wait result. */
				else if ((GL_CONDITION_SATISFIED == wait_result) || (GL_ALREADY_SIGNALED == wait_result))
				{
					/* Compare destination buffer data with reference. */
					if ((reference[0] == data_dst[0]) || (reference[1] == data_dst[1]))
					{
						is_ok = true;
					}
					else
					{
						m_context.getTestContext().getLog() << tcu::TestLog::Message << "Result data [" << data_dst[0]
															<< ", " << data_dst[1] << "is not equal to the reference ["
															<< reference[0] << ", " << reference[1] << "]. Tests fails."
															<< tcu::TestLog::EndMessage;
					}
				}
			}
			else
			{
				m_context.getTestContext().getLog()
					<< tcu::TestLog::Message << "ClientWaitSync unexpectedly generated error "
					<< glu::getErrorStr(error) << ". Tests fails." << tcu::TestLog::EndMessage;
			}
		}

		/* Unmapping. */
		gl.unmapNamedBuffer(buffer_dst);
		GLU_EXPECT_NO_ERROR(error, "glUnmapNamedBuffer have failed");
	}
	catch (...)
	{
		is_ok	= false;
		is_error = true;
	}

	/* Cleanup. */
	if (buffer_src)
	{
		gl.deleteBuffers(1, &buffer_src);
	}

	if (buffer_dst)
	{
		gl.deleteBuffers(1, &buffer_dst);
	}

	if (sync)
	{
		gl.deleteSync(sync);
	}

	/* Result's setup. */
	if (is_timeout)
	{
		m_testCtx.setTestResult(
			QP_TEST_RESULT_TIMEOUT,
			"Timeout (Potentially, ClientWaitSync with SYNC_FLUSH_COMMANDS does not return in finite time).");
	}
	else
	{
		if (is_ok)
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		}
		else
		{
			if (is_error)
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_INTERNAL_ERROR, "Error");
			}
			else
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
			}
		}
	}

	return STOP;
}
} /* Sync namespace */
} /* gl4cts namespace */