/*-------------------------------------------------------------------------
 * drawElements Quality Program EGL Module
 * ---------------------------------------
 *
 * Copyright 2016 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 Test KHR_mutable_render_buffer
 *//*--------------------------------------------------------------------*/

#include "teglMutableRenderBufferTests.hpp"

#include "egluUtil.hpp"

#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"

#include "gluDefs.hpp"
#include "gluRenderContext.hpp"

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

using namespace eglw;

using std::vector;

namespace deqp
{
namespace egl
{
namespace
{

class MutableRenderBufferTest : public TestCase
{
public:
						MutableRenderBufferTest		(EglTestContext&	eglTestCtx,
													 const char*		name,
													 const char*		description,
													 bool				enableConfigBit);
						~MutableRenderBufferTest	(void);
	void				init						(void);
	void				deinit						(void);
	IterateResult		iterate						(void);

protected:
	deUint32			drawAndSwap					(const Library&		egl,
													 deUint32			color,
													 bool				flush);
	bool				m_enableConfigBit;
	EGLDisplay			m_eglDisplay;
	EGLSurface			m_eglSurface;
	EGLConfig			m_eglConfig;
	eglu::NativeWindow*	m_window;
	EGLContext			m_eglContext;
	glw::Functions		m_gl;
};

MutableRenderBufferTest::MutableRenderBufferTest (EglTestContext& eglTestCtx,
												  const char* name, const char* description,
												  bool enableConfigBit)
	: TestCase			(eglTestCtx, name, description)
	, m_enableConfigBit	(enableConfigBit)
	, m_eglDisplay		(EGL_NO_DISPLAY)
	, m_eglSurface		(EGL_NO_SURFACE)
	, m_eglConfig		(DE_NULL)
	, m_window			(DE_NULL)
	, m_eglContext		(EGL_NO_CONTEXT)
{
}

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

void MutableRenderBufferTest::init (void)
{
	const Library&	egl	= m_eglTestCtx.getLibrary();

	// create display
	m_eglDisplay		= eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());

	if (!eglu::hasExtension(egl, m_eglDisplay, "EGL_KHR_mutable_render_buffer"))
	{
		TCU_THROW(NotSupportedError, "EGL_KHR_mutable_render_buffer is not supported");
	}

	// get mutable render buffer config
	const EGLint	attribs[]	=
	{
        EGL_RED_SIZE,			8,
        EGL_GREEN_SIZE,			8,
        EGL_BLUE_SIZE,			8,
		EGL_ALPHA_SIZE,			8,
		EGL_SURFACE_TYPE,		EGL_WINDOW_BIT | EGL_MUTABLE_RENDER_BUFFER_BIT_KHR,
		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
		EGL_NONE
	};
	const EGLint	attribsNoBit[]	=
	{
        EGL_RED_SIZE,			8,
        EGL_GREEN_SIZE,			8,
        EGL_BLUE_SIZE,			8,
		EGL_ALPHA_SIZE,			8,
		EGL_SURFACE_TYPE,		EGL_WINDOW_BIT,
		EGL_RENDERABLE_TYPE,	EGL_OPENGL_ES2_BIT,
		EGL_NONE
	};

	if (m_enableConfigBit)
	{
		m_eglConfig = eglu::chooseSingleConfig(egl, m_eglDisplay, attribs);
	}
	else
	{
		const vector<EGLConfig> configs = eglu::chooseConfigs(egl, m_eglDisplay, attribsNoBit);

		for (vector<EGLConfig>::const_iterator config = configs.begin(); config != configs.end(); ++config)
		{
			EGLint surfaceType = -1;
			EGLU_CHECK_CALL(egl, getConfigAttrib(m_eglDisplay, *config, EGL_SURFACE_TYPE, &surfaceType));

			if (!(surfaceType & EGL_MUTABLE_RENDER_BUFFER_BIT_KHR))
			{
				m_eglConfig = *config;
				break;
			}
		}

		if (m_eglConfig == DE_NULL)
			TCU_THROW(NotSupportedError, "No config without support for mutable_render_buffer found");
	}

	// create surface
	const eglu::NativeWindowFactory& factory = eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());
	m_window = factory.createWindow(&m_eglTestCtx.getNativeDisplay(), m_eglDisplay, m_eglConfig, DE_NULL,
									eglu::WindowParams(480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
	m_eglSurface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_window, m_eglDisplay, m_eglConfig, DE_NULL);

	// create context and make current
	const EGLint	contextAttribList[]	=
	{
		EGL_CONTEXT_CLIENT_VERSION, 2,
		EGL_NONE
	};

	egl.bindAPI(EGL_OPENGL_ES_API);
	m_eglContext = egl.createContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttribList);
	EGLU_CHECK_MSG(egl, "eglCreateContext");
	TCU_CHECK(m_eglSurface != EGL_NO_SURFACE);
	egl.makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
	EGLU_CHECK_MSG(egl, "eglMakeCurrent");

	m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0));
}

void MutableRenderBufferTest::deinit (void)
{
	const Library&	egl	= m_eglTestCtx.getLibrary();

	if (m_eglContext != EGL_NO_CONTEXT)
	{
		egl.makeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
		egl.destroyContext(m_eglDisplay, m_eglContext);
		m_eglContext = EGL_NO_CONTEXT;
	}

	if (m_eglSurface != EGL_NO_SURFACE)
	{
		egl.destroySurface(m_eglDisplay, m_eglSurface);
		m_eglSurface = EGL_NO_SURFACE;
	}

	if (m_eglDisplay != EGL_NO_DISPLAY)
	{
		egl.terminate(m_eglDisplay);
		m_eglDisplay = EGL_NO_DISPLAY;
	}

	if (m_window != DE_NULL)
	{
		delete m_window;
		m_window = DE_NULL;
	}
}

deUint32 MutableRenderBufferTest::drawAndSwap (const Library& egl, deUint32 color, bool flush)
{
	DE_ASSERT(color < 256);
	m_gl.clearColor((float)color/255.f, (float)color/255.f, (float)color/255.f, (float)color/255.f);
	m_gl.clear(GL_COLOR_BUFFER_BIT);
	if (flush)
	{
		m_gl.flush();
	}
	else
	{
		EGLU_CHECK_CALL(egl, swapBuffers(m_eglDisplay, m_eglSurface));
	}
	return (color | color << 8 | color << 16 | color << 24);
}

TestCase::IterateResult MutableRenderBufferTest::iterate (void)
{
	const Library&	egl	= m_eglTestCtx.getLibrary();

	int frameNumber = 1;

	// run a few back-buffered frames even if we can't verify their contents
	for (; frameNumber < 5; frameNumber++)
	{
		drawAndSwap(egl, frameNumber, false);
	}

	// switch to single-buffer rendering
	EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

	// Use eglSwapBuffers for the first frame
	drawAndSwap(egl, frameNumber, false);
	frameNumber++;

	// test a few single-buffered frames
	for (; frameNumber < 10; frameNumber++)
	{
		deUint32 backBufferPixel = 0xFFFFFFFF;
		deUint32 frontBufferPixel = drawAndSwap(egl, frameNumber, true);
		m_gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &backBufferPixel);

		// when single buffered, front-buffer == back-buffer
		if (backBufferPixel != frontBufferPixel)
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface isn't single-buffered");
			return STOP;
		}
	}

	// switch back to back-buffer rendering
	EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_BACK_BUFFER));

	// run a few back-buffered frames even if we can't verify their contents
	for (; frameNumber < 14; frameNumber++)
	{
		drawAndSwap(egl, frameNumber, false);
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

class MutableRenderBufferQueryTest : public MutableRenderBufferTest
{
public:
						MutableRenderBufferQueryTest	(EglTestContext&	eglTestCtx,
														 const char*		name,
														 const char*		description);
						~MutableRenderBufferQueryTest	(void);
	IterateResult		iterate							(void);
};

MutableRenderBufferQueryTest::MutableRenderBufferQueryTest (EglTestContext& eglTestCtx,
															const char* name, const char* description)
	: MutableRenderBufferTest	(eglTestCtx, name, description, true)
{
}

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

TestCase::IterateResult MutableRenderBufferQueryTest::iterate (void)
{
	const Library&	egl	= m_eglTestCtx.getLibrary();

	// check that by default the query returns back buffered
	EGLint curRenderBuffer = -1;
	EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
	if (curRenderBuffer != EGL_BACK_BUFFER)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't default to back-buffered rendering");
		return STOP;
	}

	// switch to single-buffer rendering and check that the query output changed
	EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
	EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
	if (curRenderBuffer != EGL_SINGLE_BUFFER)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't switch to single-buffer rendering");
		return STOP;
	}

	// switch back to back-buffer rendering and check the query again
	EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_BACK_BUFFER));
	EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
	if (curRenderBuffer != EGL_BACK_BUFFER)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't switch back to back-buffer rendering");
		return STOP;
	}
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

class MutableRenderBufferQueryNegativeTest : public MutableRenderBufferTest
{
public:
						MutableRenderBufferQueryNegativeTest	(EglTestContext&	eglTestCtx,
																 const char*		name,
																 const char*		description);
						~MutableRenderBufferQueryNegativeTest	(void);
	IterateResult		iterate									(void);
};

MutableRenderBufferQueryNegativeTest::MutableRenderBufferQueryNegativeTest (EglTestContext& eglTestCtx,
															const char* name, const char* description)
	: MutableRenderBufferTest	(eglTestCtx, name, description, false)
{
}

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

TestCase::IterateResult MutableRenderBufferQueryNegativeTest::iterate (void)
{
	const Library&	egl	= m_eglTestCtx.getLibrary();

	// check that by default the query returns back buffered
	EGLint curRenderBuffer = -1;
	EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
	if (curRenderBuffer != EGL_BACK_BUFFER)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't default to back-buffered rendering");
		return STOP;
	}

	// check that trying to switch to single-buffer rendering fails when the config bit is not set
	EGLBoolean ret = egl.surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER);
	EGLint err = egl.getError();
	if (ret != EGL_FALSE)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
			"eglSurfaceAttrib didn't return false when trying to enable single-buffering on a context without the mutable render buffer bit set");
		return STOP;
	}
	if (err != EGL_BAD_MATCH)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
			"eglSurfaceAttrib didn't set the EGL_BAD_MATCH error when trying to enable single-buffering on a context without the mutable render buffer bit set");
		return STOP;
	}

    EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
    if (curRenderBuffer != EGL_BACK_BUFFER)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't stay in back-buffered rendering after error");
        return STOP;
    }

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

} // anonymous

MutableRenderBufferTests::MutableRenderBufferTests (EglTestContext& eglTestCtx)
	: TestCaseGroup(eglTestCtx, "mutable_render_buffer", "Mutable render buffer tests")
{
}

void MutableRenderBufferTests::init (void)
{
	addChild(new MutableRenderBufferQueryTest(m_eglTestCtx, "querySurface",
		"Tests if querySurface returns the correct value after surfaceAttrib is called"));
	addChild(new MutableRenderBufferQueryNegativeTest(m_eglTestCtx, "negativeConfigBit",
		"Tests trying to enable single-buffering on a context without the mutable render buffer bit set"));
	addChild(new MutableRenderBufferTest(m_eglTestCtx, "basic",
		"Tests enabling/disabling single-buffer rendering and checks the buffering behavior", true));
}

} // egl
} // deqp