/*-------------------------------------------------------------------------
 * drawElements Quality Program EGL 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 Multi threaded EGL tests
 *//*--------------------------------------------------------------------*/
#include "teglMultiThreadTests.hpp"

#include "egluNativeWindow.hpp"
#include "egluNativePixmap.hpp"
#include "egluUtil.hpp"

#include "tcuTestLog.hpp"
#include "tcuCommandLine.hpp"

#include "deRandom.hpp"

#include "deThread.hpp"
#include "deMutex.hpp"
#include "deSemaphore.hpp"

#include "deAtomic.h"
#include "deClock.h"

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

#include <vector>
#include <set>
#include <string>
#include <sstream>

using std::vector;
using std::string;
using std::pair;
using std::set;
using std::ostringstream;

using namespace eglw;

namespace deqp
{
namespace egl
{

class ThreadLog
{
public:
	class BeginMessageToken	{};
	class EndMessageToken	{};

	struct Message
	{
					Message	(deUint64 timeUs_, const char* msg_) : timeUs(timeUs_), msg(msg_) {}

		deUint64	timeUs;
		string		msg;
	};

								ThreadLog	(void)						{ m_messages.reserve(100); }

	ThreadLog&					operator<<	(const BeginMessageToken&)	{ return *this; }
	ThreadLog&					operator<<	(const EndMessageToken&);

	template<class T>
	ThreadLog&					operator<< 	(const T& t)				{ m_message << t; return *this; }
	const vector<Message>&		getMessages (void) const				{ return m_messages; }

	static BeginMessageToken	BeginMessage;
	static EndMessageToken		EndMessage;

private:
	ostringstream		m_message;
	vector<Message>		m_messages;
};

ThreadLog& ThreadLog::operator<< (const EndMessageToken&)
{
	m_messages.push_back(Message(deGetMicroseconds(), m_message.str().c_str()));
	m_message.str("");
	return *this;
}

ThreadLog::BeginMessageToken	ThreadLog::BeginMessage;
ThreadLog::EndMessageToken		ThreadLog::EndMessage;

class MultiThreadedTest;

class TestThread : public de::Thread
{
public:
	enum ThreadStatus
	{
		THREADSTATUS_NOT_STARTED = 0,
		THREADSTATUS_RUNNING,
		THREADSTATUS_READY,

		THREADSTATUS_NOT_SUPPORTED,
		THREADSTATUS_ERROR
	};

					TestThread	(MultiThreadedTest& test, int id);
	void			run			(void);

	ThreadStatus	getStatus	(void) const	{ return m_status; }
	ThreadLog&		getLog		(void)			{ return m_log; }

	int				getId		(void) const	{ return m_id; }

	void			setStatus	(ThreadStatus status)	{ m_status = status; }

	const Library&	getLibrary	(void) const;

	// Test has stopped
	class TestStop {};


private:
	MultiThreadedTest&	m_test;
	const int			m_id;
	ThreadStatus		m_status;
	ThreadLog			m_log;
};

class MultiThreadedTest : public TestCase
{
public:
							MultiThreadedTest	(EglTestContext& eglTestCtx, const char* name, const char* description, int threadCount, deUint64 timeoutUs);
	virtual					~MultiThreadedTest	(void);

	void					init				(void);
	void					deinit				(void);

	virtual bool			runThread			(TestThread& thread) = 0;
	virtual IterateResult	iterate				(void);
	bool					execTest			(TestThread& thread);

	const Library&			getLibrary			(void) const { return m_eglTestCtx.getLibrary(); }

protected:
	void					barrier				(TestThread& thread);

private:
	int						m_threadCount;
	bool					m_initialized;
	deUint64				m_startTimeUs;
	const deUint64			m_timeoutUs;
	vector<TestThread*>		m_threads;

	volatile deInt32		m_barrierWaiters;
	de::Semaphore			m_barrierSemaphore1;
	de::Semaphore			m_barrierSemaphore2;

protected:
	EGLDisplay				m_display;
};

inline const Library& TestThread::getLibrary (void) const
{
	return m_test.getLibrary();
}

TestThread::TestThread (MultiThreadedTest& test, int id)
	: m_test	(test)
	, m_id		(id)
	, m_status	(THREADSTATUS_NOT_STARTED)
{
}

void TestThread::run (void)
{
	m_status = THREADSTATUS_RUNNING;

	try
	{
		if (m_test.execTest(*this))
			m_status = THREADSTATUS_READY;
		else
			m_status = THREADSTATUS_ERROR;
	}
	catch (const TestThread::TestStop&)
	{
		getLog() << ThreadLog::BeginMessage << "Thread stopped" << ThreadLog::EndMessage;
	}
	catch (const tcu::NotSupportedError& e)
	{
		getLog() << ThreadLog::BeginMessage << "Not supported: '" << e.what() << "'" << ThreadLog::EndMessage;
	}
	catch (const std::exception& e)
	{
		getLog() << ThreadLog::BeginMessage << "Got exception: '" << e.what() << "'" << ThreadLog::EndMessage;
	}
	catch (...)
	{
		getLog() << ThreadLog::BeginMessage << "Unknown exception" << ThreadLog::EndMessage;
	}
}

bool MultiThreadedTest::execTest (TestThread& thread)
{
	bool isOk = false;

	try
	{
		isOk = runThread(thread);
	}
	catch (const TestThread::TestStop&)
	{
		// Thread exited due to error in other thread
		throw;
	}
	catch (const tcu::NotSupportedError&)
	{
		// Set status of each thread
		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
			m_threads[threadNdx]->setStatus(TestThread::THREADSTATUS_NOT_SUPPORTED);

		// Release barriers
		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
		{
			m_barrierSemaphore1.increment();
			m_barrierSemaphore2.increment();
		}

		throw;
	}
	catch(...)
	{
		// Set status of each thread
		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
			m_threads[threadNdx]->setStatus(TestThread::THREADSTATUS_ERROR);

		// Release barriers
		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
		{
			m_barrierSemaphore1.increment();
			m_barrierSemaphore2.increment();
		}

		throw;
	}

	return isOk;
}

MultiThreadedTest::MultiThreadedTest (EglTestContext& eglTestCtx, const char* name, const char* description, int threadCount, deUint64 timeoutUs)
	: TestCase				(eglTestCtx, name, description)
	, m_threadCount			(threadCount)
	, m_initialized			(false)
	, m_startTimeUs			(0)
	, m_timeoutUs			(timeoutUs)

	, m_barrierWaiters		(0)
	, m_barrierSemaphore1	(0, 0)
	, m_barrierSemaphore2	(1, 0)

	, m_display				(EGL_NO_DISPLAY)
{
}

MultiThreadedTest::~MultiThreadedTest (void)
{
	for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
		delete m_threads[threadNdx];
	m_threads.clear();
}

void MultiThreadedTest::init (void)
{
	m_display = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());
}

void MultiThreadedTest::deinit (void)
{
	if (m_display != EGL_NO_DISPLAY)
	{
		m_eglTestCtx.getLibrary().terminate(m_display);
		m_display = EGL_NO_DISPLAY;
	}
}

void MultiThreadedTest::barrier (TestThread& thread)
{
	{
		const deInt32 waiters = deAtomicIncrement32(&m_barrierWaiters);

		if (waiters == m_threadCount)
		{
			m_barrierSemaphore2.decrement();
			m_barrierSemaphore1.increment();
		}
		else
		{
			m_barrierSemaphore1.decrement();
			m_barrierSemaphore1.increment();
		}
	}

	{
		const deInt32 waiters = deAtomicDecrement32(&m_barrierWaiters);

		if (waiters == 0)
		{
			m_barrierSemaphore1.decrement();
			m_barrierSemaphore2.increment();
		}
		else
		{
			m_barrierSemaphore2.decrement();
			m_barrierSemaphore2.increment();
		}
	}

	// Barrier was released due an error in other thread
	if (thread.getStatus() != TestThread::THREADSTATUS_RUNNING)
		throw TestThread::TestStop();
}

TestCase::IterateResult MultiThreadedTest::iterate (void)
{
	if (!m_initialized)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Thread timeout limit: " << m_timeoutUs << "us" << tcu::TestLog::EndMessage;

		// Create threads
		m_threads.reserve(m_threadCount);

		for (int threadNdx = 0; threadNdx < m_threadCount; threadNdx++)
			m_threads.push_back(new TestThread(*this, threadNdx));

		m_startTimeUs = deGetMicroseconds();

		// Run threads
		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
			m_threads[threadNdx]->start();

		m_initialized = true;
	}

	int readyCount = 0;
	for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
	{
		if (m_threads[threadNdx]->getStatus() != TestThread::THREADSTATUS_RUNNING)
			readyCount++;
	}

	if (readyCount == m_threadCount)
	{
		// Join threads
		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
			m_threads[threadNdx]->join();

		bool isOk			= true;
		bool notSupported	= false;

		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
		{
			if (m_threads[threadNdx]->getStatus() == TestThread::THREADSTATUS_ERROR)
				isOk = false;

			if (m_threads[threadNdx]->getStatus() == TestThread::THREADSTATUS_NOT_SUPPORTED)
				notSupported = true;
		}

		// Get logs
		{
			vector<int> messageNdx;

			messageNdx.resize(m_threads.size(), 0);

			while (true)
			{
				int			nextThreadNdx		= -1;
				deUint64	nextThreadTimeUs	= 0;

				for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
				{
					if (messageNdx[threadNdx] >= (int)m_threads[threadNdx]->getLog().getMessages().size())
						continue;

					if (nextThreadNdx == -1 || nextThreadTimeUs > m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs)
					{
						nextThreadNdx		= threadNdx;
						nextThreadTimeUs	= m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs;
					}
				}

				if (nextThreadNdx == -1)
					break;

				m_testCtx.getLog() << tcu::TestLog::Message << "[" << (nextThreadTimeUs - m_startTimeUs) << "] (" << nextThreadNdx << ") " << m_threads[nextThreadNdx]->getLog().getMessages()[messageNdx[nextThreadNdx]].msg << tcu::TestLog::EndMessage;

				messageNdx[nextThreadNdx]++;
			}
		}

		// Destroy threads
		for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
			delete m_threads[threadNdx];

		m_threads.clear();

		// Set result
		if (isOk)
		{
			if (notSupported)
				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not Supported");
			else
				m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		}
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

		return STOP;
	}
	else
	{
		// Check for timeout
		const deUint64 currentTimeUs = deGetMicroseconds();

		if (currentTimeUs - m_startTimeUs > m_timeoutUs)
		{
			// Get logs
			{
				vector<int> messageNdx;

				messageNdx.resize(m_threads.size(), 0);

				while (true)
				{
					int			nextThreadNdx		= -1;
					deUint64	nextThreadTimeUs	= 0;

					for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
					{
						if (messageNdx[threadNdx] >= (int)m_threads[threadNdx]->getLog().getMessages().size())
							continue;

						if (nextThreadNdx == -1 || nextThreadTimeUs > m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs)
						{
							nextThreadNdx		= threadNdx;
							nextThreadTimeUs	= m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs;
						}
					}

					if (nextThreadNdx == -1)
						break;

					m_testCtx.getLog() << tcu::TestLog::Message << "[" << (nextThreadTimeUs - m_startTimeUs) << "] (" << nextThreadNdx << ") " << m_threads[nextThreadNdx]->getLog().getMessages()[messageNdx[nextThreadNdx]].msg << tcu::TestLog::EndMessage;

					messageNdx[nextThreadNdx]++;
				}
			}

			m_testCtx.getLog() << tcu::TestLog::Message << "[" << (currentTimeUs - m_startTimeUs) << "] (-) Timeout, Limit: " << m_timeoutUs << "us" << tcu::TestLog::EndMessage;
			m_testCtx.getLog() << tcu::TestLog::Message << "[" << (currentTimeUs - m_startTimeUs) << "] (-) Trying to perform resource cleanup..." << tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
			return STOP;
		}

		// Sleep
		deSleep(10);
	}

	return CONTINUE;
}

namespace
{

const char* configAttributeToString (EGLint e)
{
	switch (e)
	{
		case EGL_BUFFER_SIZE:				return "EGL_BUFFER_SIZE";
		case EGL_RED_SIZE:					return "EGL_RED_SIZE";
		case EGL_GREEN_SIZE:				return "EGL_GREEN_SIZE";
		case EGL_BLUE_SIZE:					return "EGL_BLUE_SIZE";
		case EGL_LUMINANCE_SIZE:			return "EGL_LUMINANCE_SIZE";
		case EGL_ALPHA_SIZE:				return "EGL_ALPHA_SIZE";
		case EGL_ALPHA_MASK_SIZE:			return "EGL_ALPHA_MASK_SIZE";
		case EGL_BIND_TO_TEXTURE_RGB:		return "EGL_BIND_TO_TEXTURE_RGB";
		case EGL_BIND_TO_TEXTURE_RGBA:		return "EGL_BIND_TO_TEXTURE_RGBA";
		case EGL_COLOR_BUFFER_TYPE:			return "EGL_COLOR_BUFFER_TYPE";
		case EGL_CONFIG_CAVEAT:				return "EGL_CONFIG_CAVEAT";
		case EGL_CONFIG_ID:					return "EGL_CONFIG_ID";
		case EGL_CONFORMANT:				return "EGL_CONFORMANT";
		case EGL_DEPTH_SIZE:				return "EGL_DEPTH_SIZE";
		case EGL_LEVEL:						return "EGL_LEVEL";
		case EGL_MAX_PBUFFER_WIDTH:			return "EGL_MAX_PBUFFER_WIDTH";
		case EGL_MAX_PBUFFER_HEIGHT:		return "EGL_MAX_PBUFFER_HEIGHT";
		case EGL_MAX_PBUFFER_PIXELS:		return "EGL_MAX_PBUFFER_PIXELS";
		case EGL_MAX_SWAP_INTERVAL:			return "EGL_MAX_SWAP_INTERVAL";
		case EGL_MIN_SWAP_INTERVAL:			return "EGL_MIN_SWAP_INTERVAL";
		case EGL_NATIVE_RENDERABLE:			return "EGL_NATIVE_RENDERABLE";
		case EGL_NATIVE_VISUAL_ID:			return "EGL_NATIVE_VISUAL_ID";
		case EGL_NATIVE_VISUAL_TYPE:		return "EGL_NATIVE_VISUAL_TYPE";
		case EGL_RENDERABLE_TYPE:			return "EGL_RENDERABLE_TYPE";
		case EGL_SAMPLE_BUFFERS:			return "EGL_SAMPLE_BUFFERS";
		case EGL_SAMPLES:					return "EGL_SAMPLES";
		case EGL_STENCIL_SIZE:				return "EGL_STENCIL_SIZE";
		case EGL_SURFACE_TYPE:				return "EGL_SURFACE_TYPE";
		case EGL_TRANSPARENT_TYPE:			return "EGL_TRANSPARENT_TYPE";
		case EGL_TRANSPARENT_RED_VALUE:		return "EGL_TRANSPARENT_RED_VALUE";
		case EGL_TRANSPARENT_GREEN_VALUE:	return "EGL_TRANSPARENT_GREEN_VALUE";
		case EGL_TRANSPARENT_BLUE_VALUE:	return "EGL_TRANSPARENT_BLUE_VALUE";
		default:							return "<Unknown>";
	}
}

} // anonymous

class MultiThreadedConfigTest : public MultiThreadedTest
{
public:
				MultiThreadedConfigTest		(EglTestContext& context, const char* name, const char* description, int getConfigs, int chooseConfigs, int query);
	bool		runThread					(TestThread& thread);

private:
	const int	m_getConfigs;
	const int	m_chooseConfigs;
	const int	m_query;
};

MultiThreadedConfigTest::MultiThreadedConfigTest (EglTestContext& context, const char* name, const char* description, int getConfigs, int chooseConfigs, int query)
	: MultiThreadedTest (context, name, description, 2, 20000000/*us = 20s*/) // \todo [mika] Set timeout to something relevant to frameworks timeout?
	, m_getConfigs		(getConfigs)
	, m_chooseConfigs	(chooseConfigs)
	, m_query			(query)
{
}

bool MultiThreadedConfigTest::runThread (TestThread& thread)
{
	const Library&		egl		= getLibrary();
	de::Random			rnd		(deInt32Hash(thread.getId() + 10435));
	vector<EGLConfig>	configs;

	barrier(thread);

	for (int getConfigsNdx = 0; getConfigsNdx < m_getConfigs; getConfigsNdx++)
	{
		EGLint configCount;

		// Get number of configs
		{
			EGLBoolean result;

			result = egl.getConfigs(m_display, NULL, 0, &configCount);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigs(" << m_display << ", NULL, 0, " << configCount << ")" <<  ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglGetConfigs()");

			if (!result)
				return false;
		}

		configs.resize(configs.size() + configCount);

		// Get configs
		if (configCount != 0)
		{
			EGLBoolean result;

			result = egl.getConfigs(m_display, &(configs[configs.size() - configCount]), configCount, &configCount);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigs(" << m_display << ", &configs' " << configCount << ", " << configCount << ")" <<  ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglGetConfigs()");

			if (!result)
				return false;
		}

		// Pop configs to stop config list growing
		if (configs.size() > 40)
		{
			configs.erase(configs.begin() + 40, configs.end());
		}
		else
		{
			const int popCount = rnd.getInt(0, (int)(configs.size()-2));

			configs.erase(configs.begin() + (configs.size() - popCount), configs.end());
		}
	}

	for (int chooseConfigsNdx = 0; chooseConfigsNdx < m_chooseConfigs; chooseConfigsNdx++)
	{
		EGLint configCount;

		static const EGLint attribList[] = {
			EGL_NONE
		};

		// Get number of configs
		{
			EGLBoolean result;

			result = egl.chooseConfig(m_display, attribList, NULL, 0, &configCount);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglChooseConfig(" << m_display << ", { EGL_NONE }, NULL, 0, " << configCount << ")" <<  ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglChooseConfig()");

			if (!result)
				return false;
		}

		configs.resize(configs.size() + configCount);

		// Get configs
		if (configCount != 0)
		{
			EGLBoolean result;

			result = egl.chooseConfig(m_display, attribList, &(configs[configs.size() - configCount]), configCount, &configCount);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglChooseConfig(" << m_display << ", { EGL_NONE }, &configs, " << configCount << ", " << configCount << ")" <<  ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglChooseConfig()");

			if (!result)
				return false;
		}

		// Pop configs to stop config list growing
		if (configs.size() > 40)
		{
			configs.erase(configs.begin() + 40, configs.end());
		}
		else
		{
			const int popCount = rnd.getInt(0, (int)(configs.size()-2));

			configs.erase(configs.begin() + (configs.size() - popCount), configs.end());
		}
	}

	{
		// Perform queries on configs
		static const EGLint attributes[] =
		{
			EGL_BUFFER_SIZE,
			EGL_RED_SIZE,
			EGL_GREEN_SIZE,
			EGL_BLUE_SIZE,
			EGL_LUMINANCE_SIZE,
			EGL_ALPHA_SIZE,
			EGL_ALPHA_MASK_SIZE,
			EGL_BIND_TO_TEXTURE_RGB,
			EGL_BIND_TO_TEXTURE_RGBA,
			EGL_COLOR_BUFFER_TYPE,
			EGL_CONFIG_CAVEAT,
			EGL_CONFIG_ID,
			EGL_CONFORMANT,
			EGL_DEPTH_SIZE,
			EGL_LEVEL,
			EGL_MAX_PBUFFER_WIDTH,
			EGL_MAX_PBUFFER_HEIGHT,
			EGL_MAX_PBUFFER_PIXELS,
			EGL_MAX_SWAP_INTERVAL,
			EGL_MIN_SWAP_INTERVAL,
			EGL_NATIVE_RENDERABLE,
			EGL_NATIVE_VISUAL_ID,
			EGL_NATIVE_VISUAL_TYPE,
			EGL_RENDERABLE_TYPE,
			EGL_SAMPLE_BUFFERS,
			EGL_SAMPLES,
			EGL_STENCIL_SIZE,
			EGL_SURFACE_TYPE,
			EGL_TRANSPARENT_TYPE,
			EGL_TRANSPARENT_RED_VALUE,
			EGL_TRANSPARENT_GREEN_VALUE,
			EGL_TRANSPARENT_BLUE_VALUE
		};

		for (int queryNdx = 0; queryNdx < m_query; queryNdx++)
		{
			const EGLint	attribute	= attributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(attributes)-1)];
			EGLConfig		config		= configs[rnd.getInt(0, (int)(configs.size()-1))];
			EGLint			value;
			EGLBoolean		result;

			result = egl.getConfigAttrib(m_display, config, attribute, &value);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigAttrib(" << m_display << ", " << config << ", " << configAttributeToString(attribute) << ", " << value << ")" <<  ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglGetConfigAttrib()");

			if (!result)
				return false;
		}
	}

	return true;
}

class MultiThreadedObjectTest : public MultiThreadedTest
{
public:
	enum Type
	{
		TYPE_PBUFFER			= (1<<0),
		TYPE_PIXMAP				= (1<<1),
		TYPE_WINDOW				= (1<<2),
		TYPE_SINGLE_WINDOW		= (1<<3),
		TYPE_CONTEXT			= (1<<4)
	};

					MultiThreadedObjectTest			(EglTestContext& context, const char* name, const char* description, deUint32 types);
					~MultiThreadedObjectTest		(void);

	virtual void	deinit							(void);

	bool			runThread						(TestThread& thread);

	void			createDestroyObjects			(TestThread& thread, int count);
	void			pushObjectsToShared				(TestThread& thread);
	void			pullObjectsFromShared			(TestThread& thread, int pbufferCount, int pixmapCount, int windowCount, int contextCount);
	void			querySetSharedObjects			(TestThread& thread, int count);
	void			destroyObjects					(TestThread& thread);

private:
	EGLConfig			m_config;
	de::Random			m_rnd0;
	de::Random			m_rnd1;
	Type				m_types;

	volatile deUint32	m_hasWindow;

	vector<pair<eglu::NativePixmap*, EGLSurface> >	m_sharedNativePixmaps;
	vector<pair<eglu::NativePixmap*, EGLSurface> >	m_nativePixmaps0;
	vector<pair<eglu::NativePixmap*, EGLSurface> >	m_nativePixmaps1;

	vector<pair<eglu::NativeWindow*, EGLSurface> >	m_sharedNativeWindows;
	vector<pair<eglu::NativeWindow*, EGLSurface> >	m_nativeWindows0;
	vector<pair<eglu::NativeWindow*, EGLSurface> >	m_nativeWindows1;

	vector<EGLSurface>								m_sharedPbuffers;
	vector<EGLSurface>								m_pbuffers0;
	vector<EGLSurface>								m_pbuffers1;

	vector<EGLContext>								m_sharedContexts;
	vector<EGLContext>								m_contexts0;
	vector<EGLContext>								m_contexts1;
};

MultiThreadedObjectTest::MultiThreadedObjectTest (EglTestContext& context, const char* name, const char* description, deUint32 type)
	: MultiThreadedTest (context, name, description, 2, 20000000/*us = 20s*/) // \todo [mika] Set timeout to something relevant to frameworks timeout?
	, m_config			(DE_NULL)
	, m_rnd0			(58204327)
	, m_rnd1			(230983)
	, m_types			((Type)type)
	, m_hasWindow		(0)
{
}

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

void MultiThreadedObjectTest::deinit (void)
{
	const Library&		egl		= getLibrary();

	// Clear pbuffers
	for (int pbufferNdx = 0; pbufferNdx < (int)m_pbuffers0.size(); pbufferNdx++)
	{
		if (m_pbuffers0[pbufferNdx] != EGL_NO_SURFACE)
		{
			egl.destroySurface(m_display, m_pbuffers0[pbufferNdx]);
			EGLU_CHECK_MSG(egl, "eglDestroySurface()");
			m_pbuffers0[pbufferNdx] = EGL_NO_SURFACE;
		}
	}
	m_pbuffers0.clear();

	for (int pbufferNdx = 0; pbufferNdx < (int)m_pbuffers1.size(); pbufferNdx++)
	{
		if (m_pbuffers1[pbufferNdx] != EGL_NO_SURFACE)
		{
			egl.destroySurface(m_display, m_pbuffers1[pbufferNdx]);
			EGLU_CHECK_MSG(egl, "eglDestroySurface()");
			m_pbuffers1[pbufferNdx] = EGL_NO_SURFACE;
		}
	}
	m_pbuffers1.clear();

	for (int pbufferNdx = 0; pbufferNdx < (int)m_sharedPbuffers.size(); pbufferNdx++)
	{
		if (m_sharedPbuffers[pbufferNdx] != EGL_NO_SURFACE)
		{
			egl.destroySurface(m_display, m_sharedPbuffers[pbufferNdx]);
			EGLU_CHECK_MSG(egl, "eglDestroySurface()");
			m_sharedPbuffers[pbufferNdx] = EGL_NO_SURFACE;
		}
	}
	m_sharedPbuffers.clear();

	for (int contextNdx = 0; contextNdx < (int)m_sharedContexts.size(); contextNdx++)
	{
		if (m_sharedContexts[contextNdx] != EGL_NO_CONTEXT)
		{
			egl.destroyContext(m_display, m_sharedContexts[contextNdx]);
			EGLU_CHECK_MSG(egl, "eglDestroyContext()");
			m_sharedContexts[contextNdx] =  EGL_NO_CONTEXT;
		}
	}
	m_sharedContexts.clear();

	for (int contextNdx = 0; contextNdx < (int)m_contexts0.size(); contextNdx++)
	{
		if (m_contexts0[contextNdx] != EGL_NO_CONTEXT)
		{
			egl.destroyContext(m_display, m_contexts0[contextNdx]);
			EGLU_CHECK_MSG(egl, "eglDestroyContext()");
			m_contexts0[contextNdx] =  EGL_NO_CONTEXT;
		}
	}
	m_contexts0.clear();

	for (int contextNdx = 0; contextNdx < (int)m_contexts1.size(); contextNdx++)
	{
		if (m_contexts1[contextNdx] != EGL_NO_CONTEXT)
		{
			egl.destroyContext(m_display, m_contexts1[contextNdx]);
			EGLU_CHECK_MSG(egl, "eglDestroyContext()");
			m_contexts1[contextNdx] =  EGL_NO_CONTEXT;
		}
	}
	m_contexts1.clear();

	// Clear pixmaps
	for (int pixmapNdx = 0; pixmapNdx < (int)m_nativePixmaps0.size(); pixmapNdx++)
	{
		if (m_nativePixmaps0[pixmapNdx].second != EGL_NO_SURFACE)
			EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativePixmaps0[pixmapNdx].second));

		m_nativePixmaps0[pixmapNdx].second = EGL_NO_SURFACE;
		delete m_nativePixmaps0[pixmapNdx].first;
		m_nativePixmaps0[pixmapNdx].first = NULL;
	}
	m_nativePixmaps0.clear();

	for (int pixmapNdx = 0; pixmapNdx < (int)m_nativePixmaps1.size(); pixmapNdx++)
	{
		if (m_nativePixmaps1[pixmapNdx].second != EGL_NO_SURFACE)
			EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativePixmaps1[pixmapNdx].second));

		m_nativePixmaps1[pixmapNdx].second = EGL_NO_SURFACE;
		delete m_nativePixmaps1[pixmapNdx].first;
		m_nativePixmaps1[pixmapNdx].first = NULL;
	}
	m_nativePixmaps1.clear();

	for (int pixmapNdx = 0; pixmapNdx < (int)m_sharedNativePixmaps.size(); pixmapNdx++)
	{
		if (m_sharedNativePixmaps[pixmapNdx].second != EGL_NO_SURFACE)
			EGLU_CHECK_CALL(egl, destroySurface(m_display, m_sharedNativePixmaps[pixmapNdx].second));

		m_sharedNativePixmaps[pixmapNdx].second = EGL_NO_SURFACE;
		delete m_sharedNativePixmaps[pixmapNdx].first;
		m_sharedNativePixmaps[pixmapNdx].first = NULL;
	}
	m_sharedNativePixmaps.clear();

	// Clear windows
	for (int windowNdx = 0; windowNdx < (int)m_nativeWindows1.size(); windowNdx++)
	{
		if (m_nativeWindows1[windowNdx].second != EGL_NO_SURFACE)
			EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativeWindows1[windowNdx].second));

		m_nativeWindows1[windowNdx].second = EGL_NO_SURFACE;
		delete m_nativeWindows1[windowNdx].first;
		m_nativeWindows1[windowNdx].first = NULL;
	}
	m_nativeWindows1.clear();

	for (int windowNdx = 0; windowNdx < (int)m_nativeWindows0.size(); windowNdx++)
	{
		if (m_nativeWindows0[windowNdx].second != EGL_NO_SURFACE)
			EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativeWindows0[windowNdx].second));

		m_nativeWindows0[windowNdx].second = EGL_NO_SURFACE;
		delete m_nativeWindows0[windowNdx].first;
		m_nativeWindows0[windowNdx].first = NULL;
	}
	m_nativeWindows0.clear();

	for (int windowNdx = 0; windowNdx < (int)m_sharedNativeWindows.size(); windowNdx++)
	{
		if (m_sharedNativeWindows[windowNdx].second != EGL_NO_SURFACE)
			EGLU_CHECK_CALL(egl, destroySurface(m_display, m_sharedNativeWindows[windowNdx].second));

		m_sharedNativeWindows[windowNdx].second = EGL_NO_SURFACE;
		delete m_sharedNativeWindows[windowNdx].first;
		m_sharedNativeWindows[windowNdx].first = NULL;
	}
	m_sharedNativeWindows.clear();

	MultiThreadedTest::deinit();
}

bool MultiThreadedObjectTest::runThread (TestThread& thread)
{
	const Library&		egl		= getLibrary();

	if (thread.getId() == 0)
	{
		EGLint surfaceTypes = 0;

		if ((m_types & TYPE_WINDOW) != 0)
			surfaceTypes |= EGL_WINDOW_BIT;

		if ((m_types & TYPE_PBUFFER) != 0)
			surfaceTypes |= EGL_PBUFFER_BIT;

		if ((m_types & TYPE_PIXMAP) != 0)
			surfaceTypes |= EGL_PIXMAP_BIT;

		EGLint configCount;
		EGLint attribList[] =
		{
			EGL_SURFACE_TYPE, surfaceTypes,
			EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
			EGL_NONE
		};

		EGLU_CHECK_CALL(egl, chooseConfig(m_display, attribList, &m_config, 1, &configCount));

		if (configCount == 0)
			TCU_THROW(NotSupportedError, "No usable config found");
	}

	barrier(thread);

	// Create / Destroy Objects
	if ((m_types & TYPE_SINGLE_WINDOW) != 0 && (m_types & TYPE_PBUFFER) == 0 && (m_types & TYPE_PIXMAP) == 0 && (m_types & TYPE_CONTEXT) == 0)
	{
		if (thread.getId() == 0)
			createDestroyObjects(thread, 1);
	}
	else
		createDestroyObjects(thread, 100);

	// Push first threads objects to shared
	if (thread.getId() == 0)
		pushObjectsToShared(thread);

	barrier(thread);

	// Push second threads objects to shared
	if (thread.getId() == 1)
		pushObjectsToShared(thread);

	barrier(thread);

	// Make queries from shared surfaces
	querySetSharedObjects(thread, 100);

	barrier(thread);

	// Pull surfaces for first thread from shared surfaces
	if (thread.getId() == 0)
		pullObjectsFromShared(thread, (int)(m_sharedPbuffers.size()/2), (int)(m_sharedNativePixmaps.size()/2), (int)(m_sharedNativeWindows.size()/2), (int)(m_sharedContexts.size()/2));

	barrier(thread);

	// Pull surfaces for second thread from shared surfaces
	if (thread.getId() == 1)
		pullObjectsFromShared(thread, (int)m_sharedPbuffers.size(), (int)m_sharedNativePixmaps.size(), (int)m_sharedNativeWindows.size(), (int)m_sharedContexts.size());

	barrier(thread);

	// Create / Destroy Objects
	if ((m_types & TYPE_SINGLE_WINDOW) == 0)
		createDestroyObjects(thread, 100);

	// Destroy surfaces
	destroyObjects(thread);

	return true;
}

void MultiThreadedObjectTest::createDestroyObjects (TestThread& thread, int count)
{
	const Library&									egl			= getLibrary();
	de::Random&										rnd			= (thread.getId() == 0 ? m_rnd0 : m_rnd1);
	vector<EGLSurface>&								pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
	vector<pair<eglu::NativeWindow*, EGLSurface> >&	windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
	vector<pair<eglu::NativePixmap*, EGLSurface> >&	pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
	vector<EGLContext>&								contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);
	set<Type>										objectTypes;

	if ((m_types & TYPE_PBUFFER) != 0)
		objectTypes.insert(TYPE_PBUFFER);

	if ((m_types & TYPE_PIXMAP) != 0)
		objectTypes.insert(TYPE_PIXMAP);

	if ((m_types & TYPE_WINDOW) != 0)
		objectTypes.insert(TYPE_WINDOW);

	if ((m_types & TYPE_CONTEXT) != 0)
		objectTypes.insert(TYPE_CONTEXT);

	for (int createDestroyNdx = 0; createDestroyNdx < count; createDestroyNdx++)
	{
		bool create;
		Type type;

		if (pbuffers.size() > 5 && ((m_types & TYPE_PBUFFER) != 0))
		{
			create	= false;
			type	= TYPE_PBUFFER;
		}
		else if (windows.size() > 5 && ((m_types & TYPE_WINDOW) != 0))
		{
			create	= false;
			type	= TYPE_WINDOW;
		}
		else if (pixmaps.size() > 5 && ((m_types & TYPE_PIXMAP) != 0))
		{
			create	= false;
			type	= TYPE_PIXMAP;
		}
		else if (contexts.size() > 5 && ((m_types & TYPE_CONTEXT) != 0))
		{
			create	= false;
			type	= TYPE_CONTEXT;
		}
		else if (pbuffers.size() < 3 && ((m_types & TYPE_PBUFFER) != 0))
		{
			create	= true;
			type	= TYPE_PBUFFER;
		}
		else if (pixmaps.size() < 3 && ((m_types & TYPE_PIXMAP) != 0))
		{
			create	= true;
			type	= TYPE_PIXMAP;
		}
		else if (contexts.size() < 3 && ((m_types & TYPE_CONTEXT) != 0))
		{
			create	= true;
			type	= TYPE_CONTEXT;
		}
		else if (windows.size() < 3 && ((m_types & TYPE_WINDOW) != 0) && ((m_types & TYPE_SINGLE_WINDOW) == 0))
		{
			create	= true;
			type	= TYPE_WINDOW;
		}
		else if (windows.empty() && (m_hasWindow == 0) && ((m_types & TYPE_WINDOW) != 0) && ((m_types & TYPE_SINGLE_WINDOW) != 0))
		{
			create	= true;
			type	= TYPE_WINDOW;
		}
		else
		{
			create = rnd.getBool();

			if (!create && windows.empty())
				objectTypes.erase(TYPE_WINDOW);

			type = rnd.choose<Type>(objectTypes.begin(), objectTypes.end());
		}

		if (create)
		{
			switch (type)
			{
				case TYPE_PBUFFER:
				{
					EGLSurface surface;

					const EGLint attributes[] =
					{
						EGL_WIDTH,	64,
						EGL_HEIGHT,	64,

						EGL_NONE
					};

					surface = egl.createPbufferSurface(m_display, m_config, attributes);
					thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreatePbufferSurface(" << m_display << ", " << m_config << ", { EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE })" << ThreadLog::EndMessage;
					EGLU_CHECK_MSG(egl, "eglCreatePbufferSurface()");

					pbuffers.push_back(surface);

					break;
				}

				case TYPE_WINDOW:
				{
					const eglu::NativeWindowFactory&	windowFactory	= eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());

					if ((m_types & TYPE_SINGLE_WINDOW) != 0)
					{
						if (deAtomicCompareExchange32(&m_hasWindow, 0, 1) == 0)
						{
							eglu::NativeWindow* window	= DE_NULL;
							EGLSurface			surface = EGL_NO_SURFACE;

							try
							{
								window = windowFactory.createWindow(&m_eglTestCtx.getNativeDisplay(), m_display, m_config, DE_NULL, eglu::WindowParams(64, 64, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
								surface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, m_display, m_config, DE_NULL);

								thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreateWindowSurface()" << ThreadLog::EndMessage;
								windows.push_back(std::make_pair(window, surface));
							}
							catch (const std::exception&)
							{
								if (surface != EGL_NO_SURFACE)
									EGLU_CHECK_CALL(egl, destroySurface(m_display, surface));
								delete window;
								m_hasWindow = 0;
								throw;
							}
						}
						else
						{
							createDestroyNdx--;
						}
					}
					else
					{
						eglu::NativeWindow* window	= DE_NULL;
						EGLSurface			surface = EGL_NO_SURFACE;

						try
						{
							window	= windowFactory.createWindow(&m_eglTestCtx.getNativeDisplay(), m_display, m_config, DE_NULL, eglu::WindowParams(64, 64, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
							surface	= eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, m_display, m_config, DE_NULL);

							thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreateWindowSurface()" << ThreadLog::EndMessage;
							windows.push_back(std::make_pair(window, surface));
						}
						catch (const std::exception&)
						{
							if (surface != EGL_NO_SURFACE)
								EGLU_CHECK_CALL(egl, destroySurface(m_display, surface));
							delete window;
							throw;
						}
					}
					break;
				}

				case TYPE_PIXMAP:
				{
					const eglu::NativePixmapFactory&	pixmapFactory	= eglu::selectNativePixmapFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());
					eglu::NativePixmap* 				pixmap			= DE_NULL;
					EGLSurface							surface			= EGL_NO_SURFACE;

					try
					{
						pixmap	= pixmapFactory.createPixmap(&m_eglTestCtx.getNativeDisplay(), m_display, m_config, DE_NULL, 64, 64);
						surface	= eglu::createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, m_display, m_config, DE_NULL);

						thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreatePixmapSurface()" << ThreadLog::EndMessage;
						pixmaps.push_back(std::make_pair(pixmap, surface));
					}
					catch (const std::exception&)
					{
						if (surface != EGL_NO_SURFACE)
							EGLU_CHECK_CALL(egl, destroySurface(m_display, surface));
						delete pixmap;
						throw;
					}
					break;
				}

				case TYPE_CONTEXT:
				{
					EGLContext context;

					EGLU_CHECK_CALL(egl, bindAPI(EGL_OPENGL_ES_API));
					thread.getLog() << ThreadLog::BeginMessage << "eglBindAPI(EGL_OPENGL_ES_API)" << ThreadLog::EndMessage;

					const EGLint attributes[] =
					{
						EGL_CONTEXT_CLIENT_VERSION, 2,
						EGL_NONE
					};

					context = egl.createContext(m_display, m_config, EGL_NO_CONTEXT, attributes);
					thread.getLog() << ThreadLog::BeginMessage << context << " = eglCreateContext(" << m_display << ", " << m_config << ", EGL_NO_CONTEXT, { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE })" << ThreadLog::EndMessage;
					EGLU_CHECK_MSG(egl, "eglCreateContext()");
					contexts.push_back(context);
					break;
				}

				default:
					DE_ASSERT(false);
			};
		}
		else
		{
			switch (type)
			{
				case TYPE_PBUFFER:
				{
					const int pbufferNdx = rnd.getInt(0, (int)(pbuffers.size()-1));
					EGLBoolean result;

					result = egl.destroySurface(m_display, pbuffers[pbufferNdx]);
					thread.getLog() << ThreadLog::BeginMessage << result << " = eglDestroySurface(" << m_display << ", " << pbuffers[pbufferNdx] << ")" << ThreadLog::EndMessage;
					EGLU_CHECK_MSG(egl, "eglDestroySurface()");

					pbuffers.erase(pbuffers.begin() + pbufferNdx);

					break;
				}

				case TYPE_WINDOW:
				{
					const int windowNdx = rnd.getInt(0, (int)(windows.size()-1));

					thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << windows[windowNdx].second << ")" << ThreadLog::EndMessage;

					EGLU_CHECK_CALL(egl, destroySurface(m_display, windows[windowNdx].second));
					windows[windowNdx].second = EGL_NO_SURFACE;
					delete windows[windowNdx].first;
					windows[windowNdx].first = DE_NULL;
					windows.erase(windows.begin() + windowNdx);

					if ((m_types & TYPE_SINGLE_WINDOW) != 0)
						m_hasWindow = 0;

					break;
				}

				case TYPE_PIXMAP:
				{
					const int pixmapNdx = rnd.getInt(0, (int)(pixmaps.size()-1));

					thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << pixmaps[pixmapNdx].second << ")" << ThreadLog::EndMessage;
					EGLU_CHECK_CALL(egl, destroySurface(m_display, pixmaps[pixmapNdx].second));
					pixmaps[pixmapNdx].second = EGL_NO_SURFACE;
					delete pixmaps[pixmapNdx].first;
					pixmaps[pixmapNdx].first = DE_NULL;
					pixmaps.erase(pixmaps.begin() + pixmapNdx);

					break;
				}

				case TYPE_CONTEXT:
				{
					const int contextNdx = rnd.getInt(0, (int)(contexts.size()-1));

					EGLU_CHECK_CALL(egl, destroyContext(m_display, contexts[contextNdx]));
					thread.getLog() << ThreadLog::BeginMessage << "eglDestroyContext(" << m_display << ", " << contexts[contextNdx]  << ")" << ThreadLog::EndMessage;
					contexts.erase(contexts.begin() + contextNdx);

					break;
				}

				default:
					DE_ASSERT(false);
			}

		}
	}
}

void MultiThreadedObjectTest::pushObjectsToShared (TestThread& thread)
{
	vector<EGLSurface>&									pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
	vector<pair<eglu::NativeWindow*, EGLSurface> >&		windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
	vector<pair<eglu::NativePixmap*, EGLSurface> >&		pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
	vector<EGLContext>&									contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);

	for (int pbufferNdx = 0; pbufferNdx < (int)pbuffers.size(); pbufferNdx++)
		m_sharedPbuffers.push_back(pbuffers[pbufferNdx]);

	pbuffers.clear();

	for (int windowNdx = 0; windowNdx < (int)windows.size(); windowNdx++)
		m_sharedNativeWindows.push_back(windows[windowNdx]);

	windows.clear();

	for (int pixmapNdx = 0; pixmapNdx < (int)pixmaps.size(); pixmapNdx++)
		m_sharedNativePixmaps.push_back(pixmaps[pixmapNdx]);

	pixmaps.clear();

	for (int contextNdx = 0; contextNdx < (int)contexts.size(); contextNdx++)
		m_sharedContexts.push_back(contexts[contextNdx]);

	contexts.clear();
}

void MultiThreadedObjectTest::pullObjectsFromShared (TestThread& thread, int pbufferCount, int pixmapCount, int windowCount, int contextCount)
{
	de::Random&											rnd			= (thread.getId() == 0 ? m_rnd0 : m_rnd1);
	vector<EGLSurface>&									pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
	vector<pair<eglu::NativeWindow*, EGLSurface> >&		windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
	vector<pair<eglu::NativePixmap*, EGLSurface> >&		pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
	vector<EGLContext>&									contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);

	for (int pbufferNdx = 0; pbufferNdx < pbufferCount; pbufferNdx++)
	{
		const int ndx = rnd.getInt(0, (int)(m_sharedPbuffers.size()-1));

		pbuffers.push_back(m_sharedPbuffers[ndx]);
		m_sharedPbuffers.erase(m_sharedPbuffers.begin() + ndx);
	}

	for (int pixmapNdx = 0; pixmapNdx < pixmapCount; pixmapNdx++)
	{
		const int ndx = rnd.getInt(0, (int)(m_sharedNativePixmaps.size()-1));

		pixmaps.push_back(m_sharedNativePixmaps[ndx]);
		m_sharedNativePixmaps.erase(m_sharedNativePixmaps.begin() + ndx);
	}

	for (int windowNdx = 0; windowNdx < windowCount; windowNdx++)
	{
		const int ndx = rnd.getInt(0, (int)(m_sharedNativeWindows.size()-1));

		windows.push_back(m_sharedNativeWindows[ndx]);
		m_sharedNativeWindows.erase(m_sharedNativeWindows.begin() + ndx);
	}

	for (int contextNdx = 0; contextNdx < contextCount; contextNdx++)
	{
		const int ndx = rnd.getInt(0, (int)(m_sharedContexts.size()-1));

		contexts.push_back(m_sharedContexts[ndx]);
		m_sharedContexts.erase(m_sharedContexts.begin() + ndx);
	}
}

void MultiThreadedObjectTest::querySetSharedObjects (TestThread& thread, int count)
{
	const Library&		egl		= getLibrary();
	de::Random&			rnd		= (thread.getId() == 0 ? m_rnd0 : m_rnd1);
	vector<Type>		objectTypes;

	if ((m_types & TYPE_PBUFFER) != 0)
		objectTypes.push_back(TYPE_PBUFFER);

	if ((m_types & TYPE_PIXMAP) != 0)
		objectTypes.push_back(TYPE_PIXMAP);

	if (!m_sharedNativeWindows.empty() && (m_types & TYPE_WINDOW) != 0)
		objectTypes.push_back(TYPE_WINDOW);

	if ((m_types & TYPE_CONTEXT) != 0)
		objectTypes.push_back(TYPE_CONTEXT);

	for (int queryNdx = 0; queryNdx < count; queryNdx++)
	{
		const Type	type		= rnd.choose<Type>(objectTypes.begin(), objectTypes.end());
		EGLSurface	surface		= EGL_NO_SURFACE;
		EGLContext	context		= EGL_NO_CONTEXT;

		switch (type)
		{
			case TYPE_PBUFFER:
				surface = m_sharedPbuffers[rnd.getInt(0, (int)(m_sharedPbuffers.size()-1))];
				break;

			case TYPE_PIXMAP:
				surface = m_sharedNativePixmaps[rnd.getInt(0, (int)(m_sharedNativePixmaps.size()-1))].second;
				break;

			case TYPE_WINDOW:
				surface = m_sharedNativeWindows[rnd.getInt(0, (int)(m_sharedNativeWindows.size()-1))].second;
				break;

			case TYPE_CONTEXT:
				context = m_sharedContexts[rnd.getInt(0, (int)(m_sharedContexts.size()-1))];
				break;

			default:
				DE_ASSERT(false);
		}

		if (surface != EGL_NO_SURFACE)
		{
			static const EGLint queryAttributes[] =
			{
				EGL_LARGEST_PBUFFER,
				EGL_HEIGHT,
				EGL_WIDTH
			};

			const EGLint	attribute	= queryAttributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(queryAttributes) - 1)];
			EGLBoolean		result;
			EGLint			value;

			result = egl.querySurface(m_display, surface, attribute, &value);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglQuerySurface(" << m_display << ", " << surface << ", " << attribute << ", " << value << ")" << ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglQuerySurface()");

		}
		else if (context != EGL_NO_CONTEXT)
		{
			static const EGLint attributes[] =
			{
				EGL_CONFIG_ID,
				EGL_CONTEXT_CLIENT_TYPE,
				EGL_CONTEXT_CLIENT_VERSION,
				EGL_RENDER_BUFFER
			};

			const EGLint	attribute = attributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(attributes)-1)];
			EGLint			value;
			EGLBoolean		result;

			result = egl.queryContext(m_display, context, attribute, &value);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglQueryContext(" << m_display << ", " << context << ", " << attribute << ", " << value << ")" << ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglQueryContext()");

		}
		else
			DE_ASSERT(false);
	}
}

void MultiThreadedObjectTest::destroyObjects (TestThread& thread)
{
	const Library&										egl			= getLibrary();
	vector<EGLSurface>&									pbuffers	= (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
	vector<pair<eglu::NativeWindow*, EGLSurface> >&		windows		= (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
	vector<pair<eglu::NativePixmap*, EGLSurface> >&		pixmaps		= (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
	vector<EGLContext>&									contexts	= (thread.getId() == 0 ? m_contexts0 : m_contexts1);

	for (int pbufferNdx = 0; pbufferNdx < (int)pbuffers.size(); pbufferNdx++)
	{
		if (pbuffers[pbufferNdx] != EGL_NO_SURFACE)
		{
			// Destroy EGLSurface
			EGLBoolean result;

			result = egl.destroySurface(m_display, pbuffers[pbufferNdx]);
			thread.getLog() << ThreadLog::BeginMessage << result << " = eglDestroySurface(" << m_display << ", " << pbuffers[pbufferNdx] << ")" << ThreadLog::EndMessage;
			EGLU_CHECK_MSG(egl, "eglDestroySurface()");
			pbuffers[pbufferNdx] = EGL_NO_SURFACE;
		}
	}
	pbuffers.clear();

	for (int windowNdx = 0; windowNdx < (int)windows.size(); windowNdx++)
	{
		if (windows[windowNdx].second != EGL_NO_SURFACE)
		{
			thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << windows[windowNdx].second << ")" << ThreadLog::EndMessage;
			EGLU_CHECK_CALL(egl, destroySurface(m_display, windows[windowNdx].second));
			windows[windowNdx].second = EGL_NO_SURFACE;
		}

		if (windows[windowNdx].first)
		{
			delete windows[windowNdx].first;
			windows[windowNdx].first = NULL;
		}
	}
	windows.clear();

	for (int pixmapNdx = 0; pixmapNdx < (int)pixmaps.size(); pixmapNdx++)
	{
		if (pixmaps[pixmapNdx].first != EGL_NO_SURFACE)
		{
			thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", " << pixmaps[pixmapNdx].second << ")" << ThreadLog::EndMessage;
			EGLU_CHECK_CALL(egl, destroySurface(m_display, pixmaps[pixmapNdx].second));
			pixmaps[pixmapNdx].second = EGL_NO_SURFACE;
		}

		if (pixmaps[pixmapNdx].first)
		{
			delete pixmaps[pixmapNdx].first;
			pixmaps[pixmapNdx].first = NULL;
		}
	}
	pixmaps.clear();

	for (int contextNdx = 0; contextNdx < (int)contexts.size(); contextNdx++)
	{
		if (contexts[contextNdx] != EGL_NO_CONTEXT)
		{
			EGLU_CHECK_CALL(egl, destroyContext(m_display, contexts[contextNdx]));
			thread.getLog() << ThreadLog::BeginMessage << "eglDestroyContext(" << m_display << ", " << contexts[contextNdx]  << ")" << ThreadLog::EndMessage;
			contexts[contextNdx] = EGL_NO_CONTEXT;
		}
	}
	contexts.clear();
}

MultiThreadedTests::MultiThreadedTests (EglTestContext& context)
	: TestCaseGroup(context, "multithread", "Multithreaded EGL tests")
{
}

void MultiThreadedTests::init (void)
{
	// Config tests
	addChild(new MultiThreadedConfigTest(m_eglTestCtx,	"config",	"",	30,	30,	30));

	// Object tests
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer",								"", MultiThreadedObjectTest::TYPE_PBUFFER));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap",								"", MultiThreadedObjectTest::TYPE_PIXMAP));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"window",								"", MultiThreadedObjectTest::TYPE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"single_window",						"", MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"context",								"", MultiThreadedObjectTest::TYPE_CONTEXT));

	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap",						"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_window",						"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_single_window",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_context",						"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_CONTEXT));

	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_window",						"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_single_window",					"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_context",						"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_CONTEXT));

	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"window_context",						"", MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"single_window_context",				"", MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));

	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_window",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_single_window",			"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_context",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_CONTEXT));

	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_window_context",				"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_single_window_context",		"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));

	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_window_context",				"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pixmap_single_window_context",			"", MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));

	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_window_context",		"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
	addChild(new MultiThreadedObjectTest(m_eglTestCtx,	"pbuffer_pixmap_single_window_context",	"", MultiThreadedObjectTest::TYPE_PBUFFER|MultiThreadedObjectTest::TYPE_PIXMAP|MultiThreadedObjectTest::TYPE_WINDOW|MultiThreadedObjectTest::TYPE_SINGLE_WINDOW|MultiThreadedObjectTest::TYPE_CONTEXT));
}

} // egl
} // deqp