/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 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 Synchronization Tests
 *//*--------------------------------------------------------------------*/

#include "es31fSynchronizationTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuRenderTarget.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "gluContextInfo.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deStringUtil.hpp"
#include "deSharedPtr.hpp"
#include "deMemory.h"
#include "deRandom.hpp"

#include <map>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{


static bool validateSortedAtomicRampAdditionValueChain (const std::vector<deUint32>& valueChain, deUint32 sumValue, int& invalidOperationNdx, deUint32& errorDelta, deUint32& errorExpected)
{
	std::vector<deUint32> chainDelta(valueChain.size());

	for (int callNdx = 0; callNdx < (int)valueChain.size(); ++callNdx)
		chainDelta[callNdx] = ((callNdx + 1 == (int)valueChain.size()) ? (sumValue) : (valueChain[callNdx+1])) - valueChain[callNdx];

	// chainDelta contains now the actual additions applied to the value
	// check there exists an addition ramp form 1 to ...
	std::sort(chainDelta.begin(), chainDelta.end());

	for (int callNdx = 0; callNdx < (int)valueChain.size(); ++callNdx)
	{
		if ((int)chainDelta[callNdx] != callNdx+1)
		{
			invalidOperationNdx = callNdx;
			errorDelta = chainDelta[callNdx];
			errorExpected = callNdx+1;

			return false;
		}
	}

	return true;
}

static void readBuffer (const glw::Functions& gl, deUint32 target, int numElements, std::vector<deUint32>& result)
{
	const void* ptr = gl.mapBufferRange(target, 0, (int)(sizeof(deUint32) * numElements), GL_MAP_READ_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "map");

	if (!ptr)
		throw tcu::TestError("mapBufferRange returned NULL");

	result.resize(numElements);
	memcpy(&result[0], ptr, sizeof(deUint32) * numElements);

	if (gl.unmapBuffer(target) == GL_FALSE)
		throw tcu::TestError("unmapBuffer returned false");
}

static deUint32 readBufferUint32 (const glw::Functions& gl, deUint32 target)
{
	std::vector<deUint32> vec;

	readBuffer(gl, target, 1, vec);

	return vec[0];
}

//! Generate a ramp of values from 1 to numElements, and shuffle it
void generateShuffledRamp (int numElements, std::vector<int>& ramp)
{
	de::Random rng(0xabcd);

	// some positive (non-zero) unique values
	ramp.resize(numElements);
	for (int callNdx = 0; callNdx < numElements; ++callNdx)
		ramp[callNdx] = callNdx + 1;

	rng.shuffle(ramp.begin(), ramp.end());
}

class InterInvocationTestCase : public TestCase
{
public:
	enum StorageType
	{
		STORAGE_BUFFER = 0,
		STORAGE_IMAGE,

		STORAGE_LAST
	};
	enum CaseFlags
	{
		FLAG_ATOMIC				= 0x1,
		FLAG_ALIASING_STORAGES	= 0x2,
		FLAG_IN_GROUP			= 0x4,
	};

						InterInvocationTestCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags = 0);
						~InterInvocationTestCase	(void);

private:
	void				init						(void);
	void				deinit						(void);
	IterateResult		iterate						(void);

	void				runCompute					(void);
	bool				verifyResults				(void);
	virtual std::string	genShaderSource				(void) const = 0;

protected:
	std::string			genBarrierSource			(void) const;

	const StorageType	m_storage;
	const bool			m_useAtomic;
	const bool			m_aliasingStorages;
	const bool			m_syncWithGroup;
	const int			m_workWidth;				// !< total work width
	const int			m_workHeight;				// !<     ...    height
	const int			m_localWidth;				// !< group width
	const int			m_localHeight;				// !< group height
	const int			m_elementsPerInvocation;	// !< elements accessed by a single invocation

private:
	glw::GLuint			m_storageBuf;
	glw::GLuint			m_storageTex;
	glw::GLuint			m_resultBuf;
	glu::ShaderProgram*	m_program;
};

InterInvocationTestCase::InterInvocationTestCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
	: TestCase					(context, name, desc)
	, m_storage					(storage)
	, m_useAtomic				((flags & FLAG_ATOMIC) != 0)
	, m_aliasingStorages		((flags & FLAG_ALIASING_STORAGES) != 0)
	, m_syncWithGroup			((flags & FLAG_IN_GROUP) != 0)
	, m_workWidth				(256)
	, m_workHeight				(256)
	, m_localWidth				(16)
	, m_localHeight				(8)
	, m_elementsPerInvocation	(8)
	, m_storageBuf				(0)
	, m_storageTex				(0)
	, m_resultBuf				(0)
	, m_program					(DE_NULL)
{
	DE_ASSERT(m_storage < STORAGE_LAST);
	DE_ASSERT(m_localWidth*m_localHeight <= 128); // minimum MAX_COMPUTE_WORK_GROUP_INVOCATIONS value
}

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

void InterInvocationTestCase::init (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	// requirements

	if (m_useAtomic && m_storage == STORAGE_IMAGE && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
		throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic extension");

	// program

	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genShaderSource()));
	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		throw tcu::TestError("could not build program");

	// source

	if (m_storage == STORAGE_BUFFER)
	{
		const int				bufferElements	= m_workWidth * m_workHeight * m_elementsPerInvocation;
		const int				bufferSize		= bufferElements * sizeof(deUint32);
		std::vector<deUint32>	zeroBuffer		(bufferElements, 0);

		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating zero-filled buffer for storage, size " << bufferElements << " elements, " << bufferSize << " bytes." << tcu::TestLog::EndMessage;

		gl.genBuffers(1, &m_storageBuf);
		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_storageBuf);
		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage buf");
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		const int				bufferElements	= m_workWidth * m_workHeight * m_elementsPerInvocation;
		const int				bufferSize		= bufferElements * sizeof(deUint32);

		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating image for storage, size " << m_workWidth << "x" << m_workHeight * m_elementsPerInvocation << ", " << bufferSize << " bytes." << tcu::TestLog::EndMessage;

		gl.genTextures(1, &m_storageTex);
		gl.bindTexture(GL_TEXTURE_2D, m_storageTex);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32I, m_workWidth, m_workHeight * m_elementsPerInvocation);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage image");

		// Zero-fill
		m_testCtx.getLog() << tcu::TestLog::Message << "Filling image with 0." << tcu::TestLog::EndMessage;

		{
			const std::vector<deInt32> zeroBuffer(m_workWidth * m_workHeight * m_elementsPerInvocation, 0);
			gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_workWidth, m_workHeight * m_elementsPerInvocation, GL_RED_INTEGER, GL_INT, &zeroBuffer[0]);
			GLU_EXPECT_NO_ERROR(gl.getError(), "specify image contents");
		}
	}
	else
		DE_ASSERT(DE_FALSE);

	// destination

	{
		const int				bufferElements	= m_workWidth * m_workHeight;
		const int				bufferSize		= bufferElements * sizeof(deUint32);
		std::vector<deInt32>	negativeBuffer	(bufferElements, -1);

		m_testCtx.getLog() << tcu::TestLog::Message << "Allocating -1 filled buffer for results, size " << bufferElements << " elements, " << bufferSize << " bytes." << tcu::TestLog::EndMessage;

		gl.genBuffers(1, &m_resultBuf);
		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_resultBuf);
		gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &negativeBuffer[0], GL_STATIC_DRAW);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen storage buf");
	}
}

void InterInvocationTestCase::deinit (void)
{
	if (m_storageBuf)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_storageBuf);
		m_storageBuf = DE_NULL;
	}

	if (m_storageTex)
	{
		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_storageTex);
		m_storageTex = DE_NULL;
	}

	if (m_resultBuf)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_resultBuf);
		m_resultBuf = DE_NULL;
	}

	delete m_program;
	m_program = DE_NULL;
}

InterInvocationTestCase::IterateResult InterInvocationTestCase::iterate (void)
{
	// Dispatch
	runCompute();

	// Verify buffer contents
	if (verifyResults())
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, (std::string((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) + " content verification failed").c_str());

	return STOP;
}

void InterInvocationTestCase::runCompute (void)
{
	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
	const int				groupsX	= m_workWidth / m_localWidth;
	const int				groupsY	= m_workHeight / m_localHeight;

	DE_ASSERT((m_workWidth % m_localWidth) == 0);
	DE_ASSERT((m_workHeight % m_localHeight) == 0);

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Dispatching compute.\n"
		<< "	group size: " << m_localWidth << "x" << m_localHeight << "\n"
		<< "	dispatch size: " << groupsX << "x" << groupsY << "\n"
		<< "	total work size: " << m_workWidth << "x" << m_workHeight << "\n"
		<< tcu::TestLog::EndMessage;

	gl.useProgram(m_program->getProgram());

	// source
	if (m_storage == STORAGE_BUFFER && !m_aliasingStorages)
	{
		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageBuf);
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buf");
	}
	else if (m_storage == STORAGE_BUFFER && m_aliasingStorages)
	{
		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageBuf);
		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_storageBuf);
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buf");

		m_testCtx.getLog() << tcu::TestLog::Message << "Binding same buffer object to buffer storages." << tcu::TestLog::EndMessage;
	}
	else if (m_storage == STORAGE_IMAGE && !m_aliasingStorages)
	{
		gl.bindImageTexture(1, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");
	}
	else if (m_storage == STORAGE_IMAGE && m_aliasingStorages)
	{
		gl.bindImageTexture(1, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);
		gl.bindImageTexture(2, m_storageTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);

		GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");

		m_testCtx.getLog() << tcu::TestLog::Message << "Binding same texture level to image storages." << tcu::TestLog::EndMessage;
	}
	else
		DE_ASSERT(DE_FALSE);

	// destination
	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_resultBuf);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buf");

	// dispatch
	gl.dispatchCompute(groupsX, groupsY, 1);
	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatchCompute");
}

bool InterInvocationTestCase::verifyResults (void)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const int				errorFloodThreshold	= 5;
	int						numErrorsLogged		= 0;
	const void*				mapped				= DE_NULL;
	std::vector<deInt32>	results				(m_workWidth * m_workHeight);
	bool					error				= false;

	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_resultBuf);
	mapped = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, m_workWidth * m_workHeight * sizeof(deInt32), GL_MAP_READ_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "map buffer");

	// copy to properly aligned array
	deMemcpy(&results[0], mapped, m_workWidth * m_workHeight * sizeof(deUint32));

	if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) != GL_TRUE)
		throw tcu::TestError("memory map store corrupted");

	// check the results
	for (int ndx = 0; ndx < (int)results.size(); ++ndx)
	{
		if (results[ndx] != 1)
		{
			error = true;

			if (numErrorsLogged == 0)
				m_testCtx.getLog() << tcu::TestLog::Message << "Result buffer failed, got unexpected values.\n" << tcu::TestLog::EndMessage;
			if (numErrorsLogged++ < errorFloodThreshold)
				m_testCtx.getLog() << tcu::TestLog::Message << "	Error at index " << ndx << ": expected 1, got " << results[ndx] << ".\n" << tcu::TestLog::EndMessage;
			else
			{
				// after N errors, no point continuing verification
				m_testCtx.getLog() << tcu::TestLog::Message << "	-- too many errors, skipping verification --\n" << tcu::TestLog::EndMessage;
				break;
			}
		}
	}

	if (!error)
		m_testCtx.getLog() << tcu::TestLog::Message << "Result buffer ok." << tcu::TestLog::EndMessage;
	return !error;
}

std::string InterInvocationTestCase::genBarrierSource (void) const
{
	std::ostringstream buf;

	if (m_syncWithGroup)
	{
		// Wait until all invocations in this work group have their texture/buffer read/write operations complete
		// \note We could also use memoryBarrierBuffer() or memoryBarrierImage() in place of groupMemoryBarrier() but
		//       we only require intra-workgroup synchronization.
		buf << "\n"
			<< "	groupMemoryBarrier();\n"
			<< "	barrier();\n"
			<< "\n";
	}
	else if (m_storage == STORAGE_BUFFER)
	{
		DE_ASSERT(!m_syncWithGroup);

		// Waiting only for data written by this invocation. Since all buffer reads and writes are
		// processed in order (within a single invocation), we don't have to do anything.
		buf << "\n";
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		DE_ASSERT(!m_syncWithGroup);

		// Waiting only for data written by this invocation. But since operations complete in undefined
		// order, we have to wait for them to complete.
		buf << "\n"
			<< "	memoryBarrierImage();\n"
			<< "\n";
	}
	else
		DE_ASSERT(DE_FALSE);

	return buf.str();
}

class InvocationBasicCase : public InterInvocationTestCase
{
public:
							InvocationBasicCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
private:
	std::string				genShaderSource			(void) const;
	virtual std::string		genShaderMainBlock		(void) const = 0;
};

InvocationBasicCase::InvocationBasicCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
	: InterInvocationTestCase(context, name, desc, storage, flags)
{
}

std::string InvocationBasicCase::genShaderSource (void) const
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x=" << m_localWidth << ", local_size_y=" << m_localHeight << ") in;\n"
		<< "layout(binding=0, std430) buffer Output\n"
		<< "{\n"
		<< "	highp int values[];\n"
		<< "} sb_result;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=1, std430) coherent buffer Storage\n"
			<< "{\n"
			<< "	highp int values[];\n"
			<< "} sb_store;\n"
			<< "\n"
			<< "highp int getIndex (in highp uvec2 localID, in highp int element)\n"
			<< "{\n"
			<< "	highp uint groupNdx = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
			<< "	return int((localID.y * gl_NumWorkGroups.x * gl_NumWorkGroups.y * gl_WorkGroupSize.x) + (groupNdx * gl_WorkGroupSize.x) + localID.x) * " << m_elementsPerInvocation << " + element;\n"
			<< "}\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(r32i, binding=1) coherent uniform highp iimage2D u_image;\n"
			<< "\n"
			<< "highp ivec2 getCoord (in highp uvec2 localID, in highp int element)\n"
			<< "{\n"
			<< "	return ivec2(int(gl_WorkGroupID.x * gl_WorkGroupSize.x + localID.x), int(gl_WorkGroupID.y * gl_WorkGroupSize.y + localID.y) + element * " << m_workHeight << ");\n"
			<< "}\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	int resultNdx   = int(gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x + gl_GlobalInvocationID.x);\n"
		<< "	int groupNdx    = int(gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x);\n"
		<< "	bool allOk      = true;\n"
		<< "\n"
		<< genShaderMainBlock()
		<< "\n"
		<< "	sb_result.values[resultNdx] = (allOk) ? (1) : (0);\n"
		<< "}\n";

	return buf.str();
}

class InvocationWriteReadCase : public InvocationBasicCase
{
public:
					InvocationWriteReadCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
private:
	std::string		genShaderMainBlock			(void) const;
};

InvocationWriteReadCase::InvocationWriteReadCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
	: InvocationBasicCase(context, name, desc, storage, flags)
{
}

std::string InvocationWriteReadCase::genShaderMainBlock (void) const
{
	std::ostringstream buf;

	// write

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], groupNdx);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = groupNdx;\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), int(groupNdx));\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(int(groupNdx), 0, 0, 0));\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	// barrier

	buf << genBarrierSource();

	// read

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+1) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");

		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], 0) == groupNdx);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), 0) == groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx << ")).x == groupNdx);\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	return buf.str();
}

class InvocationReadWriteCase : public InvocationBasicCase
{
public:
					InvocationReadWriteCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
private:
	std::string		genShaderMainBlock			(void) const;
};

InvocationReadWriteCase::InvocationReadWriteCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
	: InvocationBasicCase(context, name, desc, storage, flags)
{
}

std::string InvocationReadWriteCase::genShaderMainBlock (void) const
{
	std::ostringstream buf;

	// read

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+1) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");

		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], 123) == 0);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == 0);\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), 123) == 0);\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx << ")).x == 0);\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	// barrier

	buf << genBarrierSource();

	// write

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], groupNdx);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = groupNdx;\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), int(groupNdx));\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(int(groupNdx), 0, 0, 0));\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	return buf.str();
}

class InvocationOverWriteCase : public InvocationBasicCase
{
public:
					InvocationOverWriteCase		(Context& context, const char* name, const char* desc, StorageType storage, int flags);
private:
	std::string		genShaderMainBlock			(void) const;
};

InvocationOverWriteCase::InvocationOverWriteCase (Context& context, const char* name, const char* desc, StorageType storage, int flags)
	: InvocationBasicCase(context, name, desc, storage, flags)
{
}

std::string InvocationOverWriteCase::genShaderMainBlock (void) const
{
	std::ostringstream buf;

	// write

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tatomicAdd(sb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 456);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tsb_store.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = 456;\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\timageAtomicAdd(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 456);\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\timageStore(u_image, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(456, 0, 0, 0));\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	// barrier

	buf << genBarrierSource();

	// write over

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		// write another invocation's value or our own value depending on test type
		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+4) + ", " + de::toString(3*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");

		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tatomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], groupNdx);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tsb_store.values[getIndex(" << localID << ", " << ndx << ")] = groupNdx;\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\timageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\timageStore(u_image, getCoord(" << localID << ", " << ndx << "), ivec4(groupNdx, 0, 0, 0));\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	// barrier

	buf << genBarrierSource();

	// read

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		// check another invocation's value or our own value depending on test type
		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+1) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");

		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tallOk = allOk && (atomicExchange(sb_store.values[getIndex(" << localID << ", " << ndx << ")], 123) == groupNdx);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tallOk = allOk && (sb_store.values[getIndex(" << localID << ", " << ndx << ")] == groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\tallOk = allOk && (imageAtomicExchange(u_image, getCoord(" << localID << ", " << ndx << "), 123) == groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\tallOk = allOk && (imageLoad(u_image, getCoord(" << localID << ", " << ndx << ")).x == groupNdx);\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	return buf.str();
}

class InvocationAliasWriteCase : public InterInvocationTestCase
{
public:
	enum TestType
	{
		TYPE_WRITE = 0,
		TYPE_OVERWRITE,

		TYPE_LAST
	};

					InvocationAliasWriteCase	(Context& context, const char* name, const char* desc, TestType type, StorageType storage, int flags);
private:
	std::string		genShaderSource				(void) const;

	const TestType	m_type;
};

InvocationAliasWriteCase::InvocationAliasWriteCase (Context& context, const char* name, const char* desc, TestType type, StorageType storage, int flags)
	: InterInvocationTestCase	(context, name, desc, storage, flags | FLAG_ALIASING_STORAGES)
	, m_type					(type)
{
	DE_ASSERT(type < TYPE_LAST);
}

std::string InvocationAliasWriteCase::genShaderSource (void) const
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x=" << m_localWidth << ", local_size_y=" << m_localHeight << ") in;\n"
		<< "layout(binding=0, std430) buffer Output\n"
		<< "{\n"
		<< "	highp int values[];\n"
		<< "} sb_result;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=1, std430) coherent buffer Storage0\n"
			<< "{\n"
			<< "	highp int values[];\n"
			<< "} sb_store0;\n"
			<< "layout(binding=2, std430) coherent buffer Storage1\n"
			<< "{\n"
			<< "	highp int values[];\n"
			<< "} sb_store1;\n"
			<< "\n"
			<< "highp int getIndex (in highp uvec2 localID, in highp int element)\n"
			<< "{\n"
			<< "	highp uint groupNdx = gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
			<< "	return int((localID.y * gl_NumWorkGroups.x * gl_NumWorkGroups.y * gl_WorkGroupSize.x) + (groupNdx * gl_WorkGroupSize.x) + localID.x) * " << m_elementsPerInvocation << " + element;\n"
			<< "}\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(r32i, binding=1) coherent uniform highp iimage2D u_image0;\n"
			<< "layout(r32i, binding=2) coherent uniform highp iimage2D u_image1;\n"
			<< "\n"
			<< "highp ivec2 getCoord (in highp uvec2 localID, in highp int element)\n"
			<< "{\n"
			<< "	return ivec2(int(gl_WorkGroupID.x * gl_WorkGroupSize.x + localID.x), int(gl_WorkGroupID.y * gl_WorkGroupSize.y + localID.y) + element * " << m_workHeight << ");\n"
			<< "}\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	int resultNdx   = int(gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x + gl_GlobalInvocationID.x);\n"
		<< "	int groupNdx    = int(gl_NumWorkGroups.x * gl_WorkGroupID.y + gl_WorkGroupID.x);\n"
		<< "	bool allOk      = true;\n"
		<< "\n";

	if (m_type == TYPE_OVERWRITE)
	{
		// write

		for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
		{
			if (m_storage == STORAGE_BUFFER && m_useAtomic)
				buf << "\tatomicAdd(sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 456);\n";
			else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
				buf << "\tsb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] = 456;\n";
			else if (m_storage == STORAGE_IMAGE && m_useAtomic)
				buf << "\timageAtomicAdd(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 456);\n";
			else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
				buf << "\timageStore(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << "), ivec4(456, 0, 0, 0));\n";
			else
				DE_ASSERT(DE_FALSE);
		}

		// barrier

		buf << genBarrierSource();
	}
	else
		DE_ASSERT(m_type == TYPE_WRITE);

	// write (again)

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		const std::string localID = (m_syncWithGroup) ? ("(gl_LocalInvocationID.xy + uvec2(" + de::toString(ndx+2) + ", " + de::toString(2*ndx) + ")) % gl_WorkGroupSize.xy") : ("gl_LocalInvocationID.xy");

		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tatomicExchange(sb_store1.values[getIndex(" << localID << ", " << ndx << ")], groupNdx);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tsb_store1.values[getIndex(" << localID << ", " << ndx << ")] = groupNdx;\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\timageAtomicExchange(u_image1, getCoord(" << localID << ", " << ndx << "), groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\timageStore(u_image1, getCoord(" << localID << ", " << ndx << "), ivec4(groupNdx, 0, 0, 0));\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	// barrier

	buf << genBarrierSource();

	// read

	for (int ndx = 0; ndx < m_elementsPerInvocation; ++ndx)
	{
		if (m_storage == STORAGE_BUFFER && m_useAtomic)
			buf << "\tallOk = allOk && (atomicExchange(sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")], 123) == groupNdx);\n";
		else if (m_storage == STORAGE_BUFFER && !m_useAtomic)
			buf << "\tallOk = allOk && (sb_store0.values[getIndex(gl_LocalInvocationID.xy, " << ndx << ")] == groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && m_useAtomic)
			buf << "\tallOk = allOk && (imageAtomicExchange(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << "), 123) == groupNdx);\n";
		else if (m_storage == STORAGE_IMAGE && !m_useAtomic)
			buf << "\tallOk = allOk && (imageLoad(u_image0, getCoord(gl_LocalInvocationID.xy, " << ndx << ")).x == groupNdx);\n";
		else
			DE_ASSERT(DE_FALSE);
	}

	// return result

	buf << "\n"
		<< "	sb_result.values[resultNdx] = (allOk) ? (1) : (0);\n"
		<< "}\n";

	return buf.str();
}

namespace op
{

struct WriteData
{
	int targetHandle;
	int seed;

	static WriteData Generate(int targetHandle, int seed)
	{
		WriteData retVal;

		retVal.targetHandle = targetHandle;
		retVal.seed = seed;

		return retVal;
	}
};

struct ReadData
{
	int targetHandle;
	int seed;

	static ReadData Generate(int targetHandle, int seed)
	{
		ReadData retVal;

		retVal.targetHandle = targetHandle;
		retVal.seed = seed;

		return retVal;
	}
};

struct Barrier
{
};

struct WriteDataInterleaved
{
	int		targetHandle;
	int		seed;
	bool	evenOdd;

	static WriteDataInterleaved Generate(int targetHandle, int seed, bool evenOdd)
	{
		WriteDataInterleaved retVal;

		retVal.targetHandle = targetHandle;
		retVal.seed = seed;
		retVal.evenOdd = evenOdd;

		return retVal;
	}
};

struct ReadDataInterleaved
{
	int targetHandle;
	int seed0;
	int seed1;

	static ReadDataInterleaved Generate(int targetHandle, int seed0, int seed1)
	{
		ReadDataInterleaved retVal;

		retVal.targetHandle = targetHandle;
		retVal.seed0 = seed0;
		retVal.seed1 = seed1;

		return retVal;
	}
};

struct ReadMultipleData
{
	int targetHandle0;
	int seed0;
	int targetHandle1;
	int seed1;

	static ReadMultipleData Generate(int targetHandle0, int seed0, int targetHandle1, int seed1)
	{
		ReadMultipleData retVal;

		retVal.targetHandle0 = targetHandle0;
		retVal.seed0 = seed0;
		retVal.targetHandle1 = targetHandle1;
		retVal.seed1 = seed1;

		return retVal;
	}
};

struct ReadZeroData
{
	int targetHandle;

	static ReadZeroData Generate(int targetHandle)
	{
		ReadZeroData retVal;

		retVal.targetHandle = targetHandle;

		return retVal;
	}
};

} // namespace op

class InterCallTestCase;

class InterCallOperations
{
public:
	InterCallOperations& operator<< (const op::WriteData&);
	InterCallOperations& operator<< (const op::ReadData&);
	InterCallOperations& operator<< (const op::Barrier&);
	InterCallOperations& operator<< (const op::ReadMultipleData&);
	InterCallOperations& operator<< (const op::WriteDataInterleaved&);
	InterCallOperations& operator<< (const op::ReadDataInterleaved&);
	InterCallOperations& operator<< (const op::ReadZeroData&);

private:
	struct Command
	{
		enum CommandType
		{
			TYPE_WRITE = 0,
			TYPE_READ,
			TYPE_BARRIER,
			TYPE_READ_MULTIPLE,
			TYPE_WRITE_INTERLEAVE,
			TYPE_READ_INTERLEAVE,
			TYPE_READ_ZERO,

			TYPE_LAST
		};

		CommandType type;

		union CommandUnion
		{
			op::WriteData				write;
			op::ReadData				read;
			op::Barrier					barrier;
			op::ReadMultipleData		readMulti;
			op::WriteDataInterleaved	writeInterleave;
			op::ReadDataInterleaved		readInterleave;
			op::ReadZeroData			readZero;
		} u_cmd;
	};

	friend class InterCallTestCase;

	std::vector<Command> m_cmds;
};

InterCallOperations& InterCallOperations::operator<< (const op::WriteData& cmd)
{
	m_cmds.push_back(Command());
	m_cmds.back().type = Command::TYPE_WRITE;
	m_cmds.back().u_cmd.write = cmd;

	return *this;
}

InterCallOperations& InterCallOperations::operator<< (const op::ReadData& cmd)
{
	m_cmds.push_back(Command());
	m_cmds.back().type = Command::TYPE_READ;
	m_cmds.back().u_cmd.read = cmd;

	return *this;
}

InterCallOperations& InterCallOperations::operator<< (const op::Barrier& cmd)
{
	m_cmds.push_back(Command());
	m_cmds.back().type = Command::TYPE_BARRIER;
	m_cmds.back().u_cmd.barrier = cmd;

	return *this;
}

InterCallOperations& InterCallOperations::operator<< (const op::ReadMultipleData& cmd)
{
	m_cmds.push_back(Command());
	m_cmds.back().type = Command::TYPE_READ_MULTIPLE;
	m_cmds.back().u_cmd.readMulti = cmd;

	return *this;
}

InterCallOperations& InterCallOperations::operator<< (const op::WriteDataInterleaved& cmd)
{
	m_cmds.push_back(Command());
	m_cmds.back().type = Command::TYPE_WRITE_INTERLEAVE;
	m_cmds.back().u_cmd.writeInterleave = cmd;

	return *this;
}

InterCallOperations& InterCallOperations::operator<< (const op::ReadDataInterleaved& cmd)
{
	m_cmds.push_back(Command());
	m_cmds.back().type = Command::TYPE_READ_INTERLEAVE;
	m_cmds.back().u_cmd.readInterleave = cmd;

	return *this;
}

InterCallOperations& InterCallOperations::operator<< (const op::ReadZeroData& cmd)
{
	m_cmds.push_back(Command());
	m_cmds.back().type = Command::TYPE_READ_ZERO;
	m_cmds.back().u_cmd.readZero = cmd;

	return *this;
}

class InterCallTestCase : public TestCase
{
public:
	enum StorageType
	{
		STORAGE_BUFFER = 0,
		STORAGE_IMAGE,

		STORAGE_LAST
	};
	enum Flags
	{
		FLAG_USE_ATOMIC	= 1,
		FLAG_USE_INT	= 2,
	};
													InterCallTestCase			(Context& context, const char* name, const char* desc, StorageType storage, int flags, const InterCallOperations& ops);
													~InterCallTestCase			(void);

private:
	void											init						(void);
	void											deinit						(void);
	IterateResult									iterate						(void);
	bool											verifyResults				(void);

	void											runCommand					(const op::WriteData& cmd, int stepNdx, int& programFriendlyName);
	void											runCommand					(const op::ReadData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
	void											runCommand					(const op::Barrier&);
	void											runCommand					(const op::ReadMultipleData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
	void											runCommand					(const op::WriteDataInterleaved& cmd, int stepNdx, int& programFriendlyName);
	void											runCommand					(const op::ReadDataInterleaved& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
	void											runCommand					(const op::ReadZeroData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);
	void											runSingleRead				(int targetHandle, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName);

	glw::GLuint										genStorage					(int friendlyName);
	glw::GLuint										genResultStorage			(void);
	glu::ShaderProgram*								genWriteProgram				(int seed);
	glu::ShaderProgram*								genReadProgram				(int seed);
	glu::ShaderProgram*								genReadMultipleProgram		(int seed0, int seed1);
	glu::ShaderProgram*								genWriteInterleavedProgram	(int seed, bool evenOdd);
	glu::ShaderProgram*								genReadInterleavedProgram	(int seed0, int seed1);
	glu::ShaderProgram*								genReadZeroProgram			(void);

	const StorageType								m_storage;
	const int										m_invocationGridSize;	// !< width and height of the two dimensional work dispatch
	const int										m_perInvocationSize;	// !< number of elements accessed in single invocation
	const std::vector<InterCallOperations::Command>	m_cmds;
	const bool										m_useAtomic;
	const bool										m_formatInteger;

	std::vector<glu::ShaderProgram*>				m_operationPrograms;
	std::vector<glw::GLuint>						m_operationResultStorages;
	std::map<int, glw::GLuint>						m_storageIDs;
};

InterCallTestCase::InterCallTestCase (Context& context, const char* name, const char* desc, StorageType storage, int flags, const InterCallOperations& ops)
	: TestCase					(context, name, desc)
	, m_storage					(storage)
	, m_invocationGridSize		(512)
	, m_perInvocationSize		(2)
	, m_cmds					(ops.m_cmds)
	, m_useAtomic				((flags & FLAG_USE_ATOMIC) != 0)
	, m_formatInteger			((flags & FLAG_USE_INT) != 0)
{
}

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

void InterCallTestCase::init (void)
{
	int programFriendlyName = 0;

	// requirements

	if (m_useAtomic && m_storage == STORAGE_IMAGE && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
		throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic extension");

	// generate resources and validate command list

	m_operationPrograms.resize(m_cmds.size(), DE_NULL);
	m_operationResultStorages.resize(m_cmds.size(), 0);

	for (int step = 0; step < (int)m_cmds.size(); ++step)
	{
		switch (m_cmds[step].type)
		{
			case InterCallOperations::Command::TYPE_WRITE:
			{
				const op::WriteData& cmd = m_cmds[step].u_cmd.write;

				// new storage handle?
				if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
					m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);

				// program
				{
					glu::ShaderProgram* program = genWriteProgram(cmd.seed);

					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
					m_testCtx.getLog() << *program;

					if (!program->isOk())
						throw tcu::TestError("could not build program");

					m_operationPrograms[step] = program;
				}
				break;
			}

			case InterCallOperations::Command::TYPE_READ:
			{
				const op::ReadData& cmd = m_cmds[step].u_cmd.read;
				DE_ASSERT(m_storageIDs.find(cmd.targetHandle) != m_storageIDs.end());

				// program and result storage
				{
					glu::ShaderProgram* program = genReadProgram(cmd.seed);

					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
					m_testCtx.getLog() << *program;

					if (!program->isOk())
						throw tcu::TestError("could not build program");

					m_operationPrograms[step] = program;
					m_operationResultStorages[step] = genResultStorage();
				}
				break;
			}

			case InterCallOperations::Command::TYPE_BARRIER:
			{
				break;
			}

			case InterCallOperations::Command::TYPE_READ_MULTIPLE:
			{
				const op::ReadMultipleData& cmd = m_cmds[step].u_cmd.readMulti;
				DE_ASSERT(m_storageIDs.find(cmd.targetHandle0) != m_storageIDs.end());
				DE_ASSERT(m_storageIDs.find(cmd.targetHandle1) != m_storageIDs.end());

				// program
				{
					glu::ShaderProgram* program = genReadMultipleProgram(cmd.seed0, cmd.seed1);

					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
					m_testCtx.getLog() << *program;

					if (!program->isOk())
						throw tcu::TestError("could not build program");

					m_operationPrograms[step] = program;
					m_operationResultStorages[step] = genResultStorage();
				}
				break;
			}

			case InterCallOperations::Command::TYPE_WRITE_INTERLEAVE:
			{
				const op::WriteDataInterleaved& cmd = m_cmds[step].u_cmd.writeInterleave;

				// new storage handle?
				if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
					m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);

				// program
				{
					glu::ShaderProgram* program = genWriteInterleavedProgram(cmd.seed, cmd.evenOdd);

					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
					m_testCtx.getLog() << *program;

					if (!program->isOk())
						throw tcu::TestError("could not build program");

					m_operationPrograms[step] = program;
				}
				break;
			}

			case InterCallOperations::Command::TYPE_READ_INTERLEAVE:
			{
				const op::ReadDataInterleaved& cmd = m_cmds[step].u_cmd.readInterleave;
				DE_ASSERT(m_storageIDs.find(cmd.targetHandle) != m_storageIDs.end());

				// program
				{
					glu::ShaderProgram* program = genReadInterleavedProgram(cmd.seed0, cmd.seed1);

					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
					m_testCtx.getLog() << *program;

					if (!program->isOk())
						throw tcu::TestError("could not build program");

					m_operationPrograms[step] = program;
					m_operationResultStorages[step] = genResultStorage();
				}
				break;
			}

			case InterCallOperations::Command::TYPE_READ_ZERO:
			{
				const op::ReadZeroData& cmd = m_cmds[step].u_cmd.readZero;

				// new storage handle?
				if (m_storageIDs.find(cmd.targetHandle) == m_storageIDs.end())
					m_storageIDs[cmd.targetHandle] = genStorage(cmd.targetHandle);

				// program
				{
					glu::ShaderProgram* program = genReadZeroProgram();

					m_testCtx.getLog() << tcu::TestLog::Message << "Program #" << ++programFriendlyName << tcu::TestLog::EndMessage;
					m_testCtx.getLog() << *program;

					if (!program->isOk())
						throw tcu::TestError("could not build program");

					m_operationPrograms[step] = program;
					m_operationResultStorages[step] = genResultStorage();
				}
				break;
			}

			default:
				DE_ASSERT(DE_FALSE);
		}
	}
}

void InterCallTestCase::deinit (void)
{
	// programs
	for (int ndx = 0; ndx < (int)m_operationPrograms.size(); ++ndx)
		delete m_operationPrograms[ndx];
	m_operationPrograms.clear();

	// result storages
	for (int ndx = 0; ndx < (int)m_operationResultStorages.size(); ++ndx)
	{
		if (m_operationResultStorages[ndx])
			m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_operationResultStorages[ndx]);
	}
	m_operationResultStorages.clear();

	// storage
	for (std::map<int, glw::GLuint>::const_iterator it = m_storageIDs.begin(); it != m_storageIDs.end(); ++it)
	{
		const glw::Functions& gl = m_context.getRenderContext().getFunctions();

		if (m_storage == STORAGE_BUFFER)
			gl.deleteBuffers(1, &it->second);
		else if (m_storage == STORAGE_IMAGE)
			gl.deleteTextures(1, &it->second);
		else
			DE_ASSERT(DE_FALSE);
	}
	m_storageIDs.clear();
}

InterCallTestCase::IterateResult InterCallTestCase::iterate (void)
{
	int programFriendlyName			= 0;
	int resultStorageFriendlyName	= 0;

	m_testCtx.getLog() << tcu::TestLog::Message << "Running operations:" << tcu::TestLog::EndMessage;

	// run steps

	for (int step = 0; step < (int)m_cmds.size(); ++step)
	{
		switch (m_cmds[step].type)
		{
			case InterCallOperations::Command::TYPE_WRITE:				runCommand(m_cmds[step].u_cmd.write,			step,	programFriendlyName);								break;
			case InterCallOperations::Command::TYPE_READ:				runCommand(m_cmds[step].u_cmd.read,				step,	programFriendlyName, resultStorageFriendlyName);	break;
			case InterCallOperations::Command::TYPE_BARRIER:			runCommand(m_cmds[step].u_cmd.barrier);																		break;
			case InterCallOperations::Command::TYPE_READ_MULTIPLE:		runCommand(m_cmds[step].u_cmd.readMulti,		step,	programFriendlyName, resultStorageFriendlyName);	break;
			case InterCallOperations::Command::TYPE_WRITE_INTERLEAVE:	runCommand(m_cmds[step].u_cmd.writeInterleave,	step,	programFriendlyName);								break;
			case InterCallOperations::Command::TYPE_READ_INTERLEAVE:	runCommand(m_cmds[step].u_cmd.readInterleave,	step,	programFriendlyName, resultStorageFriendlyName);	break;
			case InterCallOperations::Command::TYPE_READ_ZERO:			runCommand(m_cmds[step].u_cmd.readZero,			step,	programFriendlyName, resultStorageFriendlyName);	break;
			default:
				DE_ASSERT(DE_FALSE);
		}
	}

	// read results from result buffers
	if (verifyResults())
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, (std::string((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) + " content verification failed").c_str());

	return STOP;
}

bool InterCallTestCase::verifyResults (void)
{
	int		resultBufferFriendlyName	= 0;
	bool	allResultsOk				= true;
	bool	anyResult					= false;

	m_testCtx.getLog() << tcu::TestLog::Message << "Reading verifier program results" << tcu::TestLog::EndMessage;

	for (int step = 0; step < (int)m_cmds.size(); ++step)
	{
		const int	errorFloodThreshold	= 5;
		int			numErrorsLogged		= 0;

		if (m_operationResultStorages[step])
		{
			const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
			const void*				mapped	= DE_NULL;
			std::vector<deInt32>	results	(m_invocationGridSize * m_invocationGridSize);
			bool					error	= false;

			anyResult = true;

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_operationResultStorages[step]);
			mapped = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, m_invocationGridSize * m_invocationGridSize * sizeof(deUint32), GL_MAP_READ_BIT);
			GLU_EXPECT_NO_ERROR(gl.getError(), "map buffer");

			// copy to properly aligned array
			deMemcpy(&results[0], mapped, m_invocationGridSize * m_invocationGridSize * sizeof(deUint32));

			if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) != GL_TRUE)
				throw tcu::TestError("memory map store corrupted");

			// check the results
			for (int ndx = 0; ndx < (int)results.size(); ++ndx)
			{
				if (results[ndx] != 1)
				{
					error = true;

					if (numErrorsLogged == 0)
						m_testCtx.getLog() << tcu::TestLog::Message << "Result storage #" << ++resultBufferFriendlyName << " failed, got unexpected values.\n" << tcu::TestLog::EndMessage;
					if (numErrorsLogged++ < errorFloodThreshold)
						m_testCtx.getLog() << tcu::TestLog::Message << "	Error at index " << ndx << ": expected 1, got " << results[ndx] << ".\n" << tcu::TestLog::EndMessage;
					else
					{
						// after N errors, no point continuing verification
						m_testCtx.getLog() << tcu::TestLog::Message << "	-- too many errors, skipping verification --\n" << tcu::TestLog::EndMessage;
						break;
					}
				}
			}

			if (error)
			{
				allResultsOk = false;
			}
			else
				m_testCtx.getLog() << tcu::TestLog::Message << "Result storage #" << ++resultBufferFriendlyName << " ok." << tcu::TestLog::EndMessage;
		}
	}

	DE_ASSERT(anyResult);
	DE_UNREF(anyResult);

	return allResultsOk;
}

void InterCallTestCase::runCommand (const op::WriteData& cmd, int stepNdx, int& programFriendlyName)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Running program #" << ++programFriendlyName << " to write " << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << cmd.targetHandle << ".\n"
		<< "	Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
		<< tcu::TestLog::EndMessage;

	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());

	// set destination
	if (m_storage == STORAGE_BUFFER)
	{
		DE_ASSERT(m_storageIDs[cmd.targetHandle]);

		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_storageIDs[cmd.targetHandle]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination buffer");
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		DE_ASSERT(m_storageIDs[cmd.targetHandle]);

		gl.bindImageTexture(0, m_storageIDs[cmd.targetHandle], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_WRITE_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination image");
	}
	else
		DE_ASSERT(DE_FALSE);

	// calc
	gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch write");
}

void InterCallTestCase::runCommand (const op::ReadData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
{
	runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
}

void InterCallTestCase::runCommand (const op::Barrier& cmd)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	DE_UNREF(cmd);

	if (m_storage == STORAGE_BUFFER)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Memory Barrier\n\tbits = GL_SHADER_STORAGE_BARRIER_BIT" << tcu::TestLog::EndMessage;
		gl.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Memory Barrier\n\tbits = GL_SHADER_IMAGE_ACCESS_BARRIER_BIT" << tcu::TestLog::EndMessage;
		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
	}
	else
		DE_ASSERT(DE_FALSE);
}

void InterCallTestCase::runCommand (const op::ReadMultipleData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Running program #" << ++programFriendlyName << " to verify " << ((m_storage == STORAGE_BUFFER) ? ("buffers") : ("images")) << " #" << cmd.targetHandle0 << " and #" << cmd.targetHandle1 << ".\n"
		<< "	Writing results to result storage #" << ++resultStorageFriendlyName << ".\n"
		<< "	Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
		<< tcu::TestLog::EndMessage;

	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());

	// set sources
	if (m_storage == STORAGE_BUFFER)
	{
		DE_ASSERT(m_storageIDs[cmd.targetHandle0]);
		DE_ASSERT(m_storageIDs[cmd.targetHandle1]);

		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageIDs[cmd.targetHandle0]);
		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_storageIDs[cmd.targetHandle1]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buffers");
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		DE_ASSERT(m_storageIDs[cmd.targetHandle0]);
		DE_ASSERT(m_storageIDs[cmd.targetHandle1]);

		gl.bindImageTexture(1, m_storageIDs[cmd.targetHandle0], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
		gl.bindImageTexture(2, m_storageIDs[cmd.targetHandle1], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source images");
	}
	else
		DE_ASSERT(DE_FALSE);

	// set destination
	DE_ASSERT(m_operationResultStorages[stepNdx]);
	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_operationResultStorages[stepNdx]);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buffer");

	// calc
	gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch read multi");
}

void InterCallTestCase::runCommand (const op::WriteDataInterleaved& cmd, int stepNdx, int& programFriendlyName)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Running program #" << ++programFriendlyName << " to write " << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << cmd.targetHandle << ".\n"
		<< "	Writing to every " << ((cmd.evenOdd) ? ("even") : ("odd")) << " " << ((m_storage == STORAGE_BUFFER) ? ("element") : ("column")) << ".\n"
		<< "	Dispatch size: " << m_invocationGridSize / 2 << "x" << m_invocationGridSize << "."
		<< tcu::TestLog::EndMessage;

	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());

	// set destination
	if (m_storage == STORAGE_BUFFER)
	{
		DE_ASSERT(m_storageIDs[cmd.targetHandle]);

		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_storageIDs[cmd.targetHandle]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination buffer");
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		DE_ASSERT(m_storageIDs[cmd.targetHandle]);

		gl.bindImageTexture(0, m_storageIDs[cmd.targetHandle], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_WRITE_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind destination image");
	}
	else
		DE_ASSERT(DE_FALSE);

	// calc
	gl.dispatchCompute(m_invocationGridSize / 2, m_invocationGridSize, 1);
	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch write");
}

void InterCallTestCase::runCommand (const op::ReadDataInterleaved& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
{
	runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
}

void InterCallTestCase::runCommand (const op::ReadZeroData& cmd, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
{
	runSingleRead(cmd.targetHandle, stepNdx, programFriendlyName, resultStorageFriendlyName);
}

void InterCallTestCase::runSingleRead (int targetHandle, int stepNdx, int& programFriendlyName, int& resultStorageFriendlyName)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Running program #" << ++programFriendlyName << " to verify " << ((m_storage == STORAGE_BUFFER) ? ("buffer") : ("image")) << " #" << targetHandle << ".\n"
		<< "	Writing results to result storage #" << ++resultStorageFriendlyName << ".\n"
		<< "	Dispatch size: " << m_invocationGridSize << "x" << m_invocationGridSize << "."
		<< tcu::TestLog::EndMessage;

	gl.useProgram(m_operationPrograms[stepNdx]->getProgram());

	// set source
	if (m_storage == STORAGE_BUFFER)
	{
		DE_ASSERT(m_storageIDs[targetHandle]);

		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_storageIDs[targetHandle]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source buffer");
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		DE_ASSERT(m_storageIDs[targetHandle]);

		gl.bindImageTexture(1, m_storageIDs[targetHandle], 0, GL_FALSE, 0, (m_useAtomic) ? (GL_READ_WRITE) : (GL_READ_ONLY), (m_formatInteger) ? (GL_R32I) : (GL_R32F));
		GLU_EXPECT_NO_ERROR(gl.getError(), "bind source image");
	}
	else
		DE_ASSERT(DE_FALSE);

	// set destination
	DE_ASSERT(m_operationResultStorages[stepNdx]);
	gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_operationResultStorages[stepNdx]);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bind result buffer");

	// calc
	gl.dispatchCompute(m_invocationGridSize, m_invocationGridSize, 1);
	GLU_EXPECT_NO_ERROR(gl.getError(), "dispatch read");
}

glw::GLuint InterCallTestCase::genStorage (int friendlyName)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (m_storage == STORAGE_BUFFER)
	{
		const int		numElements		= m_invocationGridSize * m_invocationGridSize * m_perInvocationSize;
		const int		bufferSize		= numElements * ((m_formatInteger) ? (sizeof(deInt32)) : (sizeof(glw::GLfloat)));
		glw::GLuint		retVal			= 0;

		m_testCtx.getLog() << tcu::TestLog::Message << "Creating buffer #" << friendlyName << ", size " << bufferSize << " bytes." << tcu::TestLog::EndMessage;

		gl.genBuffers(1, &retVal);
		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, retVal);

		if (m_formatInteger)
		{
			const std::vector<deUint32> zeroBuffer(numElements, 0);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
		}
		else
		{
			const std::vector<float> zeroBuffer(numElements, 0.0f);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, &zeroBuffer[0], GL_STATIC_DRAW);
		}
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");

		return retVal;
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		const int	imageWidth	= m_invocationGridSize;
		const int	imageHeight	= m_invocationGridSize * m_perInvocationSize;
		glw::GLuint	retVal		= 0;

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Creating image #" << friendlyName << ", size " << imageWidth << "x" << imageHeight
			<< ", internalformat = " << ((m_formatInteger) ? ("r32i") : ("r32f"))
			<< ", size = " << (imageWidth*imageHeight*sizeof(deUint32)) << " bytes."
			<< tcu::TestLog::EndMessage;

		gl.genTextures(1, &retVal);
		gl.bindTexture(GL_TEXTURE_2D, retVal);

		if (m_formatInteger)
			gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32I, imageWidth, imageHeight);
		else
			gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32F, imageWidth, imageHeight);

		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen image");

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Filling image with 0"
			<< tcu::TestLog::EndMessage;

		if (m_formatInteger)
		{
			const std::vector<deInt32> zeroBuffer(imageWidth * imageHeight, 0);
			gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imageWidth, imageHeight, GL_RED_INTEGER, GL_INT, &zeroBuffer[0]);
		}
		else
		{
			const std::vector<float> zeroBuffer(imageWidth * imageHeight, 0.0f);
			gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imageWidth, imageHeight, GL_RED, GL_FLOAT, &zeroBuffer[0]);
		}

		GLU_EXPECT_NO_ERROR(gl.getError(), "specify image contents");

		return retVal;
	}
	else
	{
		DE_ASSERT(DE_FALSE);
		return 0;
	}
}

glw::GLuint InterCallTestCase::genResultStorage (void)
{
	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
	glw::GLuint				retVal	= 0;

	gl.genBuffers(1, &retVal);
	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, retVal);
	gl.bufferData(GL_SHADER_STORAGE_BUFFER, m_invocationGridSize * m_invocationGridSize * sizeof(deUint32), DE_NULL, GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");

	return retVal;
}

glu::ShaderProgram* InterCallTestCase::genWriteProgram (int seed)
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=0, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
			<< "{\n"
			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
			<< "} sb_out;\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=0) " << ((m_useAtomic) ? ("coherent ") : ("writeonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageOut;\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	uvec3 size    = gl_NumWorkGroups * gl_WorkGroupSize;\n"
		<< "	int groupNdx  = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
		<< "\n";

	// Write to buffer/image m_perInvocationSize elements
	if (m_storage == STORAGE_BUFFER)
	{
		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
		{
			if (m_useAtomic)
				buf << "	atomicExchange(";
			else
				buf << "	";

			buf << "sb_out.values[(groupNdx + " << seed + writeNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "]";

			if (m_useAtomic)
				buf << ", " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
			else
				buf << " = " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx);\n";
		}
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
		{
			if (m_useAtomic)
				buf << "	imageAtomicExchange";
			else
				buf << "	imageStore";

			buf << "(u_imageOut, ivec2((int(gl_GlobalInvocationID.x) + " << (seed + writeNdx*100) << ") % " << m_invocationGridSize << ", int(gl_GlobalInvocationID.y) + " << writeNdx*m_invocationGridSize << "), ";

			if (m_useAtomic)
				buf << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
			else
				buf << ((m_formatInteger) ? ("ivec4(int(groupNdx), 0, 0, 0)") : ("vec4(float(groupNdx), 0.0, 0.0, 0.0)")) << ");\n";
		}
	}
	else
		DE_ASSERT(DE_FALSE);

	buf << "}\n";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
}

glu::ShaderProgram* InterCallTestCase::genReadProgram (int seed)
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
			<< "{\n"
			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
			<< "} sb_in;\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
		<< "{\n"
		<< "	highp int resultOk[];\n"
		<< "} sb_result;\n"
		<< "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0")) << ";\n"
		<< "	bool allOk = true;\n"
		<< "\n";

	// Verify data

	if (m_storage == STORAGE_BUFFER)
	{
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
		{
			if (!m_useAtomic)
				buf << "	allOk = allOk && (sb_in.values[(groupNdx + "
					<< seed + readNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "] == "
					<< ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
			else
				buf << "	allOk = allOk && (atomicExchange(sb_in.values[(groupNdx + "
					<< seed + readNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "], zero) == "
					<< ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
		}
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
		{
			if (!m_useAtomic)
				buf	<< "	allOk = allOk && (imageLoad(u_imageIn, ivec2((gl_GlobalInvocationID.x + "
					<< (seed + readNdx*100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx*m_invocationGridSize << "u)).x == "
					<< ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
			else
				buf << "	allOk = allOk && (imageAtomicExchange(u_imageIn, ivec2((gl_GlobalInvocationID.x + "
					<< (seed + readNdx*100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx*m_invocationGridSize << "u), zero) == "
					<< ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
		}
	}
	else
		DE_ASSERT(DE_FALSE);

	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
		<< "}\n";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
}

glu::ShaderProgram* InterCallTestCase::genReadMultipleProgram (int seed0, int seed1)
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer0\n"
			<< "{\n"
			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
			<< "} sb_in0;\n"
			<< "layout(binding=2, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer1\n"
			<< "{\n"
			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
			<< "} sb_in1;\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn0;\n"
			<< "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=2) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn1;\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
		<< "{\n"
		<< "	highp int resultOk[];\n"
		<< "} sb_result;\n"
		<< "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0")) << ";\n"
		<< "	bool allOk = true;\n"
		<< "\n";

	// Verify data

	if (m_storage == STORAGE_BUFFER)
	{
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "	allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in0.values[(groupNdx + " << seed0 + readNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "]" << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n"
				<< "	allOk = allOk && (" << ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in1.values[(groupNdx + " << seed1 + readNdx*m_invocationGridSize*m_invocationGridSize << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize << "]" << ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "	allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad")) << "(u_imageIn0, ivec2((gl_GlobalInvocationID.x + " << (seed0 + readNdx*100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx*m_invocationGridSize << "u)" << ((m_useAtomic) ? (", zero)") : (").x")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n"
				<< "	allOk = allOk && (" << ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad")) << "(u_imageIn1, ivec2((gl_GlobalInvocationID.x + " << (seed1 + readNdx*100) << "u) % " << m_invocationGridSize << "u, gl_GlobalInvocationID.y + " << readNdx*m_invocationGridSize << "u)" << ((m_useAtomic) ? (", zero)") : (").x")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
	}
	else
		DE_ASSERT(DE_FALSE);

	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
		<< "}\n";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
}

glu::ShaderProgram* InterCallTestCase::genWriteInterleavedProgram (int seed, bool evenOdd)
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=0, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
			<< "{\n"
			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
			<< "} sb_out;\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=0) " << ((m_useAtomic) ? ("coherent ") : ("writeonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageOut;\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	uvec3 size    = gl_NumWorkGroups * gl_WorkGroupSize;\n"
		<< "	int groupNdx  = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
		<< "\n";

	// Write to buffer/image m_perInvocationSize elements
	if (m_storage == STORAGE_BUFFER)
	{
		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
		{
			if (m_useAtomic)
				buf << "	atomicExchange(";
			else
				buf << "	";

			buf << "sb_out.values[((groupNdx + " << seed + writeNdx*m_invocationGridSize*m_invocationGridSize / 2 << ") % " << m_invocationGridSize*m_invocationGridSize / 2 * m_perInvocationSize  << ") * 2 + " << ((evenOdd) ? (0) : (1)) << "]";

			if (m_useAtomic)
				buf << ", " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
			else
				buf << "= " << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx);\n";
		}
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		for (int writeNdx = 0; writeNdx < m_perInvocationSize; ++writeNdx)
		{
			if (m_useAtomic)
				buf << "	imageAtomicExchange";
			else
				buf << "	imageStore";

			buf << "(u_imageOut, ivec2(((int(gl_GlobalInvocationID.x) + " << (seed + writeNdx*100) << ") % " << m_invocationGridSize / 2 << ") * 2 + " << ((evenOdd) ? (0) : (1)) << ", int(gl_GlobalInvocationID.y) + " << writeNdx*m_invocationGridSize << "), ";

			if (m_useAtomic)
				buf << ((m_formatInteger) ? ("int") : ("float")) << "(groupNdx));\n";
			else
				buf << ((m_formatInteger) ? ("ivec4(int(groupNdx), 0, 0, 0)") : ("vec4(float(groupNdx), 0.0, 0.0, 0.0)")) << ");\n";
		}
	}
	else
		DE_ASSERT(DE_FALSE);

	buf << "}\n";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
}

glu::ShaderProgram* InterCallTestCase::genReadInterleavedProgram (int seed0, int seed1)
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
			<< "{\n"
			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
			<< "} sb_in;\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
		<< "{\n"
		<< "	highp int resultOk[];\n"
		<< "} sb_result;\n"
		<< "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
		<< "	int interleavedGroupNdx = int((size.x >> 1U) * size.y * gl_GlobalInvocationID.z + (size.x >> 1U) * gl_GlobalInvocationID.y + (gl_GlobalInvocationID.x >> 1U));\n"
		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " zero = " << ((m_formatInteger) ? ("0") : ("0.0")) << ";\n"
		<< "	bool allOk = true;\n"
		<< "\n";

	// Verify data

	if (m_storage == STORAGE_BUFFER)
	{
		buf << "	if (groupNdx % 2 == 0)\n"
			<< "	{\n";
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "		allOk = allOk && ("
				<< ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in.values[((interleavedGroupNdx + " << seed0 + readNdx*m_invocationGridSize*m_invocationGridSize / 2 << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize / 2 << ") * 2 + 0]"
				<< ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
		buf << "	}\n"
			<< "	else\n"
			<< "	{\n";
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "		allOk = allOk && ("
				<< ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in.values[((interleavedGroupNdx + " << seed1 + readNdx*m_invocationGridSize*m_invocationGridSize / 2 << ") % " << m_invocationGridSize*m_invocationGridSize*m_perInvocationSize / 2 << ") * 2 + 1]"
				<< ((m_useAtomic) ? (", zero)") : ("")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
		buf << "	}\n";
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		buf << "	if (groupNdx % 2 == 0)\n"
			<< "	{\n";
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "		allOk = allOk && ("
				<< ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
				<< "(u_imageIn, ivec2(((int(gl_GlobalInvocationID.x >> 1U) + " << (seed0 + readNdx*100) << ") % " << m_invocationGridSize / 2 << ") * 2 + 0, int(gl_GlobalInvocationID.y) + " << readNdx*m_invocationGridSize << ")"
				<< ((m_useAtomic) ? (", zero)") : (").x")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
		buf << "	}\n"
			<< "	else\n"
			<< "	{\n";
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "		allOk = allOk && ("
				<< ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad"))
				<< "(u_imageIn, ivec2(((int(gl_GlobalInvocationID.x >> 1U) + " << (seed1 + readNdx*100) << ") % " << m_invocationGridSize / 2 << ") * 2 + 1, int(gl_GlobalInvocationID.y) + " << readNdx*m_invocationGridSize << ")"
				<< ((m_useAtomic) ? (", zero)") : (").x")) << " == " << ((m_formatInteger) ? ("int") : ("float")) << "(interleavedGroupNdx));\n";
		buf << "	}\n";
	}
	else
		DE_ASSERT(DE_FALSE);

	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
		<< "}\n";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
}

glu::ShaderProgram*	InterCallTestCase::genReadZeroProgram (void)
{
	const bool			useImageAtomics = m_useAtomic && m_storage == STORAGE_IMAGE;
	std::ostringstream	buf;

	buf << "#version 310 es\n"
		<< ((useImageAtomics) ? ("#extension GL_OES_shader_image_atomic : require\n") : (""))
		<< "layout (local_size_x = 1, local_size_y = 1) in;\n";

	if (m_storage == STORAGE_BUFFER)
		buf << "layout(binding=1, std430) " << ((m_useAtomic) ? ("coherent ") : ("")) << "buffer Buffer\n"
			<< "{\n"
			<< "	highp " << ((m_formatInteger) ? ("int") : ("float")) << " values[];\n"
			<< "} sb_in;\n";
	else if (m_storage == STORAGE_IMAGE)
		buf << "layout(" << ((m_formatInteger) ? ("r32i") : ("r32f")) << ", binding=1) " << ((m_useAtomic) ? ("coherent ") : ("readonly ")) << "uniform highp " << ((m_formatInteger) ? ("iimage2D") : ("image2D")) << " u_imageIn;\n";
	else
		DE_ASSERT(DE_FALSE);

	buf << "layout(binding=0, std430) buffer ResultBuffer\n"
		<< "{\n"
		<< "	highp int resultOk[];\n"
		<< "} sb_result;\n"
		<< "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
		<< "	int groupNdx = int(size.x * size.y * gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x);\n"
		<< "	" << ((m_formatInteger) ? ("int") : ("float")) << " anything = " << ((m_formatInteger) ? ("5") : ("5.0")) << ";\n"
		<< "	bool allOk = true;\n"
		<< "\n";

	// Verify data

	if (m_storage == STORAGE_BUFFER)
	{
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "	allOk = allOk && ("
				<< ((m_useAtomic) ? ("atomicExchange(") : ("")) << "sb_in.values[groupNdx * " << m_perInvocationSize << " + " << readNdx << "]"
				<< ((m_useAtomic) ? (", anything)") : ("")) << " == " << ((m_formatInteger) ? ("0") : ("0.0")) << ");\n";
	}
	else if (m_storage == STORAGE_IMAGE)
	{
		for (int readNdx = 0; readNdx < m_perInvocationSize; ++readNdx)
			buf << "	allOk = allOk && ("
			<< ((m_useAtomic) ? ("imageAtomicExchange") : ("imageLoad")) << "(u_imageIn, ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y + " << (readNdx*m_invocationGridSize) << "u)"
			<< ((m_useAtomic) ? (", anything)") : (").x")) << " == " << ((m_formatInteger) ? ("0") : ("0.0")) << ");\n";
	}
	else
		DE_ASSERT(DE_FALSE);

	buf << "	sb_result.resultOk[groupNdx] = (allOk) ? (1) : (0);\n"
		<< "}\n";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(buf.str()));
}

class SSBOConcurrentAtomicCase : public TestCase
{
public:

							SSBOConcurrentAtomicCase	(Context& context, const char* name, const char* description, int numCalls, int workSize);
							~SSBOConcurrentAtomicCase	(void);

	void					init						(void);
	void					deinit						(void);
	IterateResult			iterate						(void);

private:
	std::string				genComputeSource			(void) const;

	const int				m_numCalls;
	const int				m_workSize;
	glu::ShaderProgram*		m_program;
	deUint32				m_bufferID;
	std::vector<deUint32>	m_intermediateResultBuffers;
};

SSBOConcurrentAtomicCase::SSBOConcurrentAtomicCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
	: TestCase		(context, name, description)
	, m_numCalls	(numCalls)
	, m_workSize	(workSize)
	, m_program		(DE_NULL)
	, m_bufferID	(DE_NULL)
{
}

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

void SSBOConcurrentAtomicCase::init (void)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	std::vector<deUint32>	zeroData			(m_workSize, 0);

	// gen buffers

	gl.genBuffers(1, &m_bufferID);
	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
	gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);

	for (int ndx = 0; ndx < m_numCalls; ++ndx)
	{
		deUint32 buffer = 0;

		gl.genBuffers(1, &buffer);
		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
		gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);

		m_intermediateResultBuffers.push_back(buffer);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
	}

	// gen program

	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource()));
	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		throw tcu::TestError("could not build program");
}

void SSBOConcurrentAtomicCase::deinit (void)
{
	if (m_bufferID)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufferID);
		m_bufferID = 0;
	}

	for (int ndx = 0; ndx < (int)m_intermediateResultBuffers.size(); ++ndx)
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffers[ndx]);
	m_intermediateResultBuffers.clear();

	delete m_program;
	m_program = DE_NULL;
}

TestCase::IterateResult SSBOConcurrentAtomicCase::iterate (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	const deUint32			sumValue		= (deUint32)(m_numCalls * (m_numCalls + 1) / 2);
	std::vector<int>		deltas;

	// generate unique deltas
	generateShuffledRamp(m_numCalls, deltas);

	// invoke program N times, each with a different delta
	{
		const int deltaLocation = gl.getUniformLocation(m_program->getProgram(), "u_atomicDelta");

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Running shader " << m_numCalls << " times.\n"
			<< "Num groups = (" << m_workSize << ", 1, 1)\n"
			<< "Setting u_atomicDelta to a unique value for each call.\n"
			<< tcu::TestLog::EndMessage;

		if (deltaLocation == -1)
			throw tcu::TestError("u_atomicDelta location was -1");

		gl.useProgram(m_program->getProgram());
		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_bufferID);

		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
		{
			m_testCtx.getLog()
				<< tcu::TestLog::Message
				<< "Call " << callNdx << ": u_atomicDelta = " << deltas[callNdx]
				<< tcu::TestLog::EndMessage;

			gl.uniform1ui(deltaLocation, deltas[callNdx]);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffers[callNdx]);
			gl.dispatchCompute(m_workSize, 1, 1);
		}

		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
	}

	// Verify result
	{
		std::vector<deUint32> result;

		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be filled with value " << sumValue << tcu::TestLog::EndMessage;

		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
		readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize, result);

		for (int ndx = 0; ndx < m_workSize; ++ndx)
		{
			if (result[ndx] != sumValue)
			{
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Work buffer error, at index " << ndx << " expected value " << (sumValue) << ", got " << result[ndx] << "\n"
					<< "Work buffer contains invalid values."
					<< tcu::TestLog::EndMessage;

				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
				return STOP;
			}
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Work buffer contents are valid." << tcu::TestLog::EndMessage;
	}

	// verify steps
	{
		std::vector<std::vector<deUint32> >	intermediateResults	(m_numCalls);
		std::vector<deUint32>				valueChain			(m_numCalls);

		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;

		// collect results

		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
		{
			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffers[callNdx]);
			readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize, intermediateResults[callNdx]);
		}

		// verify values

		for (int valueNdx = 0; valueNdx < m_workSize; ++valueNdx)
		{
			int			invalidOperationNdx;
			deUint32	errorDelta;
			deUint32	errorExpected;

			// collect result chain for each element
			for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
				valueChain[callNdx] = intermediateResults[callNdx][valueNdx];

			// check there exists a path from 0 to sumValue using each addition once
			// decompose cumulative results to addition operations (all additions positive => this works)

			std::sort(valueChain.begin(), valueChain.end());

			// validate chain
			if (!validateSortedAtomicRampAdditionValueChain(valueChain, sumValue, invalidOperationNdx, errorDelta, errorExpected))
			{
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Intermediate buffer error, at value index " << valueNdx << ", applied operation index " << invalidOperationNdx << ", value was increased by " << errorDelta << ", but expected " << errorExpected << ".\n"
					<< "Intermediate buffer contains invalid values. Values at index " << valueNdx << "\n"
					<< tcu::TestLog::EndMessage;

				for (int logCallNdx = 0; logCallNdx < m_numCalls; ++logCallNdx)
					m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx << "] = " << intermediateResults[logCallNdx][valueNdx] << tcu::TestLog::EndMessage;
				m_testCtx.getLog() << tcu::TestLog::Message << "Result = " << sumValue << tcu::TestLog::EndMessage;

				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
				return STOP;
			}
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
	}

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

std::string SSBOConcurrentAtomicCase::genComputeSource (void) const
{
	std::ostringstream buf;

	buf	<< "#version 310 es\n"
		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
		<< "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
		<< "{\n"
		<< "	highp uint values[" << m_workSize << "];\n"
		<< "} sb_ires;\n"
		<< "\n"
		<< "layout (binding = 2, std430) volatile buffer WorkBuffer\n"
		<< "{\n"
		<< "	highp uint values[" << m_workSize << "];\n"
		<< "} sb_work;\n"
		<< "uniform highp uint u_atomicDelta;\n"
		<< "\n"
		<< "void main ()\n"
		<< "{\n"
		<< "	highp uint invocationIndex = gl_GlobalInvocationID.x;\n"
		<< "	sb_ires.values[invocationIndex] = atomicAdd(sb_work.values[invocationIndex], u_atomicDelta);\n"
		<< "}";

	return buf.str();
}

class ConcurrentAtomicCounterCase : public TestCase
{
public:

							ConcurrentAtomicCounterCase		(Context& context, const char* name, const char* description, int numCalls, int workSize);
							~ConcurrentAtomicCounterCase	(void);

	void					init							(void);
	void					deinit							(void);
	IterateResult			iterate							(void);

private:
	std::string				genComputeSource				(bool evenOdd) const;

	const int				m_numCalls;
	const int				m_workSize;
	glu::ShaderProgram*		m_evenProgram;
	glu::ShaderProgram*		m_oddProgram;
	deUint32				m_counterBuffer;
	deUint32				m_intermediateResultBuffer;
};

ConcurrentAtomicCounterCase::ConcurrentAtomicCounterCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
	: TestCase					(context, name, description)
	, m_numCalls				(numCalls)
	, m_workSize				(workSize)
	, m_evenProgram				(DE_NULL)
	, m_oddProgram				(DE_NULL)
	, m_counterBuffer			(DE_NULL)
	, m_intermediateResultBuffer(DE_NULL)
{
}

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

void ConcurrentAtomicCounterCase::init (void)
{
	const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
	const std::vector<deUint32>	zeroData	(m_numCalls * m_workSize, 0);

	// gen buffer

	gl.genBuffers(1, &m_counterBuffer);
	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_counterBuffer);
	gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32), &zeroData[0], GL_DYNAMIC_COPY);

	gl.genBuffers(1, &m_intermediateResultBuffer);
	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffer);
	gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_numCalls * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);

	GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");

	// gen programs

	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "EvenProgram", "Even program");

		m_evenProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource(true)));
		m_testCtx.getLog() << *m_evenProgram;
		if (!m_evenProgram->isOk())
			throw tcu::TestError("could not build program");
	}
	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "OddProgram", "Odd program");

		m_oddProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource(false)));
		m_testCtx.getLog() << *m_oddProgram;
		if (!m_oddProgram->isOk())
			throw tcu::TestError("could not build program");
	}
}

void ConcurrentAtomicCounterCase::deinit (void)
{
	if (m_counterBuffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_counterBuffer);
		m_counterBuffer = 0;
	}
	if (m_intermediateResultBuffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffer);
		m_intermediateResultBuffer = 0;
	}

	delete m_evenProgram;
	m_evenProgram = DE_NULL;

	delete m_oddProgram;
	m_oddProgram = DE_NULL;
}

TestCase::IterateResult ConcurrentAtomicCounterCase::iterate (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	// invoke program N times, each with a different delta
	{
		const int evenCallNdxLocation	= gl.getUniformLocation(m_evenProgram->getProgram(), "u_callNdx");
		const int oddCallNdxLocation	= gl.getUniformLocation(m_oddProgram->getProgram(), "u_callNdx");

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Running shader pair (even & odd) " << m_numCalls << " times.\n"
			<< "Num groups = (" << m_workSize << ", 1, 1)\n"
			<< tcu::TestLog::EndMessage;

		if (evenCallNdxLocation == -1)
			throw tcu::TestError("u_callNdx location was -1");
		if (oddCallNdxLocation == -1)
			throw tcu::TestError("u_callNdx location was -1");

		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffer);
		gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, m_counterBuffer);

		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
		{
			gl.useProgram(m_evenProgram->getProgram());
			gl.uniform1ui(evenCallNdxLocation, (deUint32)callNdx);
			gl.dispatchCompute(m_workSize, 1, 1);

			gl.useProgram(m_oddProgram->getProgram());
			gl.uniform1ui(oddCallNdxLocation, (deUint32)callNdx);
			gl.dispatchCompute(m_workSize, 1, 1);
		}

		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
	}

	// Verify result
	{
		deUint32 result;

		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be " << m_numCalls*m_workSize << tcu::TestLog::EndMessage;

		gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_counterBuffer);
		result = readBufferUint32(gl, GL_ATOMIC_COUNTER_BUFFER);

		if ((int)result != m_numCalls*m_workSize)
		{
			m_testCtx.getLog()
				<< tcu::TestLog::Message
				<< "Counter buffer error, expected value " << (m_numCalls*m_workSize) << ", got " << result << "\n"
				<< tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
			return STOP;
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Counter buffer is valid." << tcu::TestLog::EndMessage;
	}

	// verify steps
	{
		std::vector<deUint32> intermediateResults;

		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;

		// collect results

		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffer);
		readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_numCalls * m_workSize, intermediateResults);

		// verify values

		std::sort(intermediateResults.begin(), intermediateResults.end());

		for (int valueNdx = 0; valueNdx < m_workSize * m_numCalls; ++valueNdx)
		{
			if ((int)intermediateResults[valueNdx] != valueNdx)
			{
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Intermediate buffer error, at value index " << valueNdx << ", expected " << valueNdx << ", got " << intermediateResults[valueNdx] << ".\n"
					<< "Intermediate buffer contains invalid values. Intermediate results:\n"
					<< tcu::TestLog::EndMessage;

				for (int logCallNdx = 0; logCallNdx < m_workSize * m_numCalls; ++logCallNdx)
					m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx << "] = " << intermediateResults[logCallNdx] << tcu::TestLog::EndMessage;

				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
				return STOP;
			}
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
	}

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

std::string ConcurrentAtomicCounterCase::genComputeSource (bool evenOdd) const
{
	std::ostringstream buf;

	buf	<< "#version 310 es\n"
		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
		<< "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
		<< "{\n"
		<< "	highp uint values[" << m_workSize * m_numCalls << "];\n"
		<< "} sb_ires;\n"
		<< "\n"
		<< "layout (binding = 2, offset = 0) uniform atomic_uint u_counter;\n"
		<< "uniform highp uint u_callNdx;\n"
		<< "\n"
		<< "void main ()\n"
		<< "{\n"
		<< "	highp uint dataNdx = u_callNdx * " << m_workSize << "u + gl_GlobalInvocationID.x;\n"
		<< "	if ((dataNdx % 2u) == " << ((evenOdd) ? (0) : (1)) << "u)\n"
		<< "		sb_ires.values[dataNdx] = atomicCounterIncrement(u_counter);\n"
		<< "}";

	return buf.str();
}

class ConcurrentImageAtomicCase : public TestCase
{
public:

							ConcurrentImageAtomicCase	(Context& context, const char* name, const char* description, int numCalls, int workSize);
							~ConcurrentImageAtomicCase	(void);

	void					init						(void);
	void					deinit						(void);
	IterateResult			iterate						(void);

private:
	void					readWorkImage				(std::vector<deUint32>& result);

	std::string				genComputeSource			(void) const;
	std::string				genImageReadSource			(void) const;
	std::string				genImageClearSource			(void) const;

	const int				m_numCalls;
	const int				m_workSize;
	glu::ShaderProgram*		m_program;
	glu::ShaderProgram*		m_imageReadProgram;
	glu::ShaderProgram*		m_imageClearProgram;
	deUint32				m_imageID;
	std::vector<deUint32>	m_intermediateResultBuffers;
};

ConcurrentImageAtomicCase::ConcurrentImageAtomicCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
	: TestCase				(context, name, description)
	, m_numCalls			(numCalls)
	, m_workSize			(workSize)
	, m_program				(DE_NULL)
	, m_imageReadProgram	(DE_NULL)
	, m_imageClearProgram	(DE_NULL)
	, m_imageID				(DE_NULL)
{
}

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

void ConcurrentImageAtomicCase::init (void)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	std::vector<deUint32>	zeroData			(m_workSize * m_workSize, 0);

	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
		throw tcu::NotSupportedError("Test requires GL_OES_shader_image_atomic");

	// gen image

	gl.genTextures(1, &m_imageID);
	gl.bindTexture(GL_TEXTURE_2D, m_imageID);
	gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_workSize, m_workSize);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");

	// gen buffers

	for (int ndx = 0; ndx < m_numCalls; ++ndx)
	{
		deUint32 buffer = 0;

		gl.genBuffers(1, &buffer);
		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
		gl.bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(deUint32) * m_workSize * m_workSize, &zeroData[0], GL_DYNAMIC_COPY);

		m_intermediateResultBuffers.push_back(buffer);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");
	}

	// gen programs

	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genComputeSource()));
	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		throw tcu::TestError("could not build program");

	m_imageReadProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genImageReadSource()));
	if (!m_imageReadProgram->isOk())
	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "ImageReadProgram", "Image read program");

		m_testCtx.getLog() << *m_imageReadProgram;
		throw tcu::TestError("could not build program");
	}

	m_imageClearProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genImageClearSource()));
	if (!m_imageClearProgram->isOk())
	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "ImageClearProgram", "Image read program");

		m_testCtx.getLog() << *m_imageClearProgram;
		throw tcu::TestError("could not build program");
	}
}

void ConcurrentImageAtomicCase::deinit (void)
{
	if (m_imageID)
	{
		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_imageID);
		m_imageID = 0;
	}

	for (int ndx = 0; ndx < (int)m_intermediateResultBuffers.size(); ++ndx)
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_intermediateResultBuffers[ndx]);
	m_intermediateResultBuffers.clear();

	delete m_program;
	m_program = DE_NULL;

	delete m_imageReadProgram;
	m_imageReadProgram = DE_NULL;

	delete m_imageClearProgram;
	m_imageClearProgram = DE_NULL;
}

TestCase::IterateResult ConcurrentImageAtomicCase::iterate (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	const deUint32			sumValue		= (deUint32)(m_numCalls * (m_numCalls + 1) / 2);
	std::vector<int>		deltas;

	// generate unique deltas
	generateShuffledRamp(m_numCalls, deltas);

	// clear image
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Clearing image contents" << tcu::TestLog::EndMessage;

		gl.useProgram(m_imageClearProgram->getProgram());
		gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
		gl.dispatchCompute(m_workSize, m_workSize, 1);
		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

		GLU_EXPECT_NO_ERROR(gl.getError(), "clear");
	}

	// invoke program N times, each with a different delta
	{
		const int deltaLocation = gl.getUniformLocation(m_program->getProgram(), "u_atomicDelta");

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Running shader " << m_numCalls << " times.\n"
			<< "Num groups = (" << m_workSize << ", " << m_workSize << ", 1)\n"
			<< "Setting u_atomicDelta to a unique value for each call.\n"
			<< tcu::TestLog::EndMessage;

		if (deltaLocation == -1)
			throw tcu::TestError("u_atomicDelta location was -1");

		gl.useProgram(m_program->getProgram());
		gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);

		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
		{
			m_testCtx.getLog()
				<< tcu::TestLog::Message
				<< "Call " << callNdx << ": u_atomicDelta = " << deltas[callNdx]
				<< tcu::TestLog::EndMessage;

			gl.uniform1ui(deltaLocation, deltas[callNdx]);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_intermediateResultBuffers[callNdx]);
			gl.dispatchCompute(m_workSize, m_workSize, 1);
		}

		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
	}

	// Verify result
	{
		std::vector<deUint32> result;

		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work image, it should be filled with value " << sumValue << tcu::TestLog::EndMessage;

		readWorkImage(result);

		for (int ndx = 0; ndx < m_workSize * m_workSize; ++ndx)
		{
			if (result[ndx] != sumValue)
			{
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Work image error, at index (" << ndx % m_workSize << ", " << ndx / m_workSize << ") expected value " << (sumValue) << ", got " << result[ndx] << "\n"
					<< "Work image contains invalid values."
					<< tcu::TestLog::EndMessage;

				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image contents invalid");
				return STOP;
			}
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Work image contents are valid." << tcu::TestLog::EndMessage;
	}

	// verify steps
	{
		std::vector<std::vector<deUint32> >	intermediateResults	(m_numCalls);
		std::vector<deUint32>				valueChain			(m_numCalls);
		std::vector<deUint32>				chainDelta			(m_numCalls);

		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying intermediate results. " << tcu::TestLog::EndMessage;

		// collect results

		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
		{
			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_intermediateResultBuffers[callNdx]);
			readBuffer(gl, GL_SHADER_STORAGE_BUFFER, m_workSize * m_workSize, intermediateResults[callNdx]);
		}

		// verify values

		for (int valueNdx = 0; valueNdx < m_workSize; ++valueNdx)
		{
			int			invalidOperationNdx;
			deUint32	errorDelta;
			deUint32	errorExpected;

			// collect result chain for each element
			for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
				valueChain[callNdx] = intermediateResults[callNdx][valueNdx];

			// check there exists a path from 0 to sumValue using each addition once
			// decompose cumulative results to addition operations (all additions positive => this works)

			std::sort(valueChain.begin(), valueChain.end());

			for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
				chainDelta[callNdx] = ((callNdx + 1 == m_numCalls) ? (sumValue) : (valueChain[callNdx+1])) - valueChain[callNdx];

			// chainDelta contains now the actual additions applied to the value
			std::sort(chainDelta.begin(), chainDelta.end());

			// validate chain
			if (!validateSortedAtomicRampAdditionValueChain(valueChain, sumValue, invalidOperationNdx, errorDelta, errorExpected))
			{
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Intermediate buffer error, at index (" << valueNdx % m_workSize << ", " << valueNdx / m_workSize << "), applied operation index "
					<< invalidOperationNdx << ", value was increased by " << errorDelta << ", but expected " << errorExpected << ".\n"
					<< "Intermediate buffer contains invalid values. Values at index (" << valueNdx % m_workSize << ", " << valueNdx / m_workSize << ")\n"
					<< tcu::TestLog::EndMessage;

				for (int logCallNdx = 0; logCallNdx < m_numCalls; ++logCallNdx)
					m_testCtx.getLog() << tcu::TestLog::Message << "Value[" << logCallNdx << "] = " << intermediateResults[logCallNdx][valueNdx] << tcu::TestLog::EndMessage;
				m_testCtx.getLog() << tcu::TestLog::Message << "Result = " << sumValue << tcu::TestLog::EndMessage;

				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
				return STOP;
			}
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Intermediate buffers are valid." << tcu::TestLog::EndMessage;
	}

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

void ConcurrentImageAtomicCase::readWorkImage (std::vector<deUint32>& result)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	glu::Buffer				resultBuffer	(m_context.getRenderContext());

	// Read image to an ssbo

	{
		const std::vector<deUint32> zeroData(m_workSize*m_workSize, 0);

		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *resultBuffer);
		gl.bufferData(GL_SHADER_STORAGE_BUFFER, (int)(sizeof(deUint32) * m_workSize * m_workSize), &zeroData[0], GL_DYNAMIC_COPY);

		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
		gl.useProgram(m_imageReadProgram->getProgram());

		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *resultBuffer);
		gl.bindImageTexture(2, m_imageID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
		gl.dispatchCompute(m_workSize, m_workSize, 1);

		GLU_EXPECT_NO_ERROR(gl.getError(), "read");
	}

	// Read ssbo
	{
		const void* ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, (int)(sizeof(deUint32) * m_workSize * m_workSize), GL_MAP_READ_BIT);
		GLU_EXPECT_NO_ERROR(gl.getError(), "map");

		if (!ptr)
			throw tcu::TestError("mapBufferRange returned NULL");

		result.resize(m_workSize * m_workSize);
		memcpy(&result[0], ptr, sizeof(deUint32) * m_workSize * m_workSize);

		if (gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER) == GL_FALSE)
			throw tcu::TestError("unmapBuffer returned false");
	}
}

std::string ConcurrentImageAtomicCase::genComputeSource (void) const
{
	std::ostringstream buf;

	buf	<< "#version 310 es\n"
		<< "#extension GL_OES_shader_image_atomic : require\n"
		<< "\n"
		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
		<< "layout (binding = 1, std430) writeonly buffer IntermediateResults\n"
		<< "{\n"
		<< "	highp uint values[" << m_workSize * m_workSize << "];\n"
		<< "} sb_ires;\n"
		<< "\n"
		<< "layout (binding = 2, r32ui) volatile uniform highp uimage2D u_workImage;\n"
		<< "uniform highp uint u_atomicDelta;\n"
		<< "\n"
		<< "void main ()\n"
		<< "{\n"
		<< "	highp uint invocationIndex = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * uint(" << m_workSize <<");\n"
		<< "	sb_ires.values[invocationIndex] = imageAtomicAdd(u_workImage, ivec2(gl_GlobalInvocationID.xy), u_atomicDelta);\n"
		<< "}";

	return buf.str();
}

std::string ConcurrentImageAtomicCase::genImageReadSource (void) const
{
	std::ostringstream buf;

	buf	<< "#version 310 es\n"
		<< "\n"
		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
		<< "layout (binding = 1, std430) writeonly buffer ImageValues\n"
		<< "{\n"
		<< "	highp uint values[" << m_workSize * m_workSize << "];\n"
		<< "} sb_res;\n"
		<< "\n"
		<< "layout (binding = 2, r32ui) readonly uniform highp uimage2D u_workImage;\n"
		<< "\n"
		<< "void main ()\n"
		<< "{\n"
		<< "	highp uint invocationIndex = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * uint(" << m_workSize <<");\n"
		<< "	sb_res.values[invocationIndex] = imageLoad(u_workImage, ivec2(gl_GlobalInvocationID.xy)).x;\n"
		<< "}";

	return buf.str();
}

std::string ConcurrentImageAtomicCase::genImageClearSource (void) const
{
	std::ostringstream buf;

	buf	<< "#version 310 es\n"
		<< "\n"
		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
		<< "layout (binding = 2, r32ui) writeonly uniform highp uimage2D u_workImage;\n"
		<< "\n"
		<< "void main ()\n"
		<< "{\n"
		<< "	imageStore(u_workImage, ivec2(gl_GlobalInvocationID.xy), uvec4(0, 0, 0, 0));\n"
		<< "}";

	return buf.str();
}

class ConcurrentSSBOAtomicCounterMixedCase : public TestCase
{
public:
							ConcurrentSSBOAtomicCounterMixedCase	(Context& context, const char* name, const char* description, int numCalls, int workSize);
							~ConcurrentSSBOAtomicCounterMixedCase	(void);

	void					init									(void);
	void					deinit									(void);
	IterateResult			iterate									(void);

private:
	std::string				genSSBOComputeSource					(void) const;
	std::string				genAtomicCounterComputeSource			(void) const;

	const int				m_numCalls;
	const int				m_workSize;
	deUint32				m_bufferID;
	glu::ShaderProgram*		m_ssboAtomicProgram;
	glu::ShaderProgram*		m_atomicCounterProgram;
};

ConcurrentSSBOAtomicCounterMixedCase::ConcurrentSSBOAtomicCounterMixedCase (Context& context, const char* name, const char* description, int numCalls, int workSize)
	: TestCase					(context, name, description)
	, m_numCalls				(numCalls)
	, m_workSize				(workSize)
	, m_bufferID				(DE_NULL)
	, m_ssboAtomicProgram		(DE_NULL)
	, m_atomicCounterProgram	(DE_NULL)
{
	// SSBO atomic XORs cancel out
	DE_ASSERT((workSize * numCalls) % (16 * 2) == 0);
}

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

void ConcurrentSSBOAtomicCounterMixedCase::init (void)
{
	const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();
	const deUint32				zeroBuf[2]	= { 0, 0 };

	// gen buffer

	gl.genBuffers(1, &m_bufferID);
	gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_bufferID);
	gl.bufferData(GL_SHADER_STORAGE_BUFFER, (int)(sizeof(deUint32) * 2), zeroBuf, GL_DYNAMIC_COPY);

	GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffers");

	// gen programs

	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "SSBOProgram", "SSBO atomic program");

		m_ssboAtomicProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genSSBOComputeSource()));
		m_testCtx.getLog() << *m_ssboAtomicProgram;
		if (!m_ssboAtomicProgram->isOk())
			throw tcu::TestError("could not build program");
	}
	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "AtomicCounterProgram", "Atomic counter program");

		m_atomicCounterProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genAtomicCounterComputeSource()));
		m_testCtx.getLog() << *m_atomicCounterProgram;
		if (!m_atomicCounterProgram->isOk())
			throw tcu::TestError("could not build program");
	}
}

void ConcurrentSSBOAtomicCounterMixedCase::deinit (void)
{
	if (m_bufferID)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_bufferID);
		m_bufferID = 0;
	}

	delete m_ssboAtomicProgram;
	m_ssboAtomicProgram = DE_NULL;

	delete m_atomicCounterProgram;
	m_atomicCounterProgram = DE_NULL;
}

TestCase::IterateResult ConcurrentSSBOAtomicCounterMixedCase::iterate (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	m_testCtx.getLog() << tcu::TestLog::Message << "Testing atomic counters and SSBO atomic operations with both backed by the same buffer." << tcu::TestLog::EndMessage;

	// invoke programs N times
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Running SSBO atomic program and atomic counter program " << m_numCalls << " times. (interleaved)\n"
			<< "Num groups = (" << m_workSize << ", 1, 1)\n"
			<< tcu::TestLog::EndMessage;

		gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_bufferID);
		gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, m_bufferID);

		for (int callNdx = 0; callNdx < m_numCalls; ++callNdx)
		{
			gl.useProgram(m_atomicCounterProgram->getProgram());
			gl.dispatchCompute(m_workSize, 1, 1);

			gl.useProgram(m_ssboAtomicProgram->getProgram());
			gl.dispatchCompute(m_workSize, 1, 1);
		}

		GLU_EXPECT_NO_ERROR(gl.getError(), "post dispatch");
	}

	// Verify result
	{
		deUint32 result;

		// XORs cancel out, only addition is left
		m_testCtx.getLog() << tcu::TestLog::Message << "Verifying work buffer, it should be " << m_numCalls*m_workSize << tcu::TestLog::EndMessage;

		gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_bufferID);
		result = readBufferUint32(gl, GL_ATOMIC_COUNTER_BUFFER);

		if ((int)result != m_numCalls*m_workSize)
		{
			m_testCtx.getLog()
				<< tcu::TestLog::Message
				<< "Buffer value error, expected value " << (m_numCalls*m_workSize) << ", got " << result << "\n"
				<< tcu::TestLog::EndMessage;

			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents invalid");
			return STOP;
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Buffer is valid." << tcu::TestLog::EndMessage;
	}

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

std::string ConcurrentSSBOAtomicCounterMixedCase::genSSBOComputeSource (void) const
{
	std::ostringstream buf;

	buf	<< "#version 310 es\n"
		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
		<< "layout (binding = 1, std430) volatile buffer WorkBuffer\n"
		<< "{\n"
		<< "	highp uint targetValue;\n"
		<< "	highp uint dummy;\n"
		<< "} sb_work;\n"
		<< "\n"
		<< "void main ()\n"
		<< "{\n"
		<< "	// flip high bits\n"
		<< "	highp uint mask = uint(1) << (16u + (gl_GlobalInvocationID.x % 16u));\n"
		<< "	sb_work.dummy = atomicXor(sb_work.targetValue, mask);\n"
		<< "}";

	return buf.str();
}

std::string ConcurrentSSBOAtomicCounterMixedCase::genAtomicCounterComputeSource (void) const
{
	std::ostringstream buf;

	buf	<< "#version 310 es\n"
		<< "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
		<< "\n"
		<< "layout (binding = 2, offset = 0) uniform atomic_uint u_counter;\n"
		<< "\n"
		<< "void main ()\n"
		<< "{\n"
		<< "	atomicCounterIncrement(u_counter);\n"
		<< "}";

	return buf.str();
}

} // anonymous

SynchronizationTests::SynchronizationTests (Context& context)
	: TestCaseGroup(context, "synchronization", "Synchronization tests")
{
}

SynchronizationTests::~SynchronizationTests (void)
{
}

void SynchronizationTests::init (void)
{
	tcu::TestCaseGroup* const inInvocationGroup		= new tcu::TestCaseGroup(m_testCtx, "in_invocation",	"Test intra-invocation synchronization");
	tcu::TestCaseGroup* const interInvocationGroup	= new tcu::TestCaseGroup(m_testCtx, "inter_invocation", "Test inter-invocation synchronization");
	tcu::TestCaseGroup* const interCallGroup		= new tcu::TestCaseGroup(m_testCtx, "inter_call",       "Test inter-call synchronization");

	addChild(inInvocationGroup);
	addChild(interInvocationGroup);
	addChild(interCallGroup);

	// .in_invocation & .inter_invocation
	{
		static const struct CaseConfig
		{
			const char*									namePrefix;
			const InterInvocationTestCase::StorageType	storage;
			const int									flags;
		} configs[] =
		{
			{ "image",			InterInvocationTestCase::STORAGE_IMAGE,		0										},
			{ "image_atomic",	InterInvocationTestCase::STORAGE_IMAGE,		InterInvocationTestCase::FLAG_ATOMIC	},
			{ "ssbo",			InterInvocationTestCase::STORAGE_BUFFER,	0										},
			{ "ssbo_atomic",	InterInvocationTestCase::STORAGE_BUFFER,	InterInvocationTestCase::FLAG_ATOMIC	},
		};

		for (int groupNdx = 0; groupNdx < 2; ++groupNdx)
		{
			tcu::TestCaseGroup* const	targetGroup	= (groupNdx == 0) ? (inInvocationGroup) : (interInvocationGroup);
			const int					extraFlags	= (groupNdx == 0) ? (0) : (InterInvocationTestCase::FLAG_IN_GROUP);

			for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
			{
				const char* const target = (configs[configNdx].storage == InterInvocationTestCase::STORAGE_BUFFER) ? ("buffer") : ("image");

				targetGroup->addChild(new InvocationWriteReadCase(m_context,
																  (std::string(configs[configNdx].namePrefix) + "_write_read").c_str(),
																  (std::string("Write to ") + target + " and read it").c_str(),
																  configs[configNdx].storage,
																  configs[configNdx].flags | extraFlags));

				targetGroup->addChild(new InvocationReadWriteCase(m_context,
																  (std::string(configs[configNdx].namePrefix) + "_read_write").c_str(),
																  (std::string("Read form ") + target + " and then write to it").c_str(),
																  configs[configNdx].storage,
																  configs[configNdx].flags | extraFlags));

				targetGroup->addChild(new InvocationOverWriteCase(m_context,
																  (std::string(configs[configNdx].namePrefix) + "_overwrite").c_str(),
																  (std::string("Write to ") + target + " twice and read it").c_str(),
																  configs[configNdx].storage,
																  configs[configNdx].flags | extraFlags));

				targetGroup->addChild(new InvocationAliasWriteCase(m_context,
																   (std::string(configs[configNdx].namePrefix) + "_alias_write").c_str(),
																   (std::string("Write to aliasing ") + target + " and read it").c_str(),
																   InvocationAliasWriteCase::TYPE_WRITE,
																   configs[configNdx].storage,
																   configs[configNdx].flags | extraFlags));

				targetGroup->addChild(new InvocationAliasWriteCase(m_context,
																   (std::string(configs[configNdx].namePrefix) + "_alias_overwrite").c_str(),
																   (std::string("Write to aliasing ") + target + "s and read it").c_str(),
																   InvocationAliasWriteCase::TYPE_OVERWRITE,
																   configs[configNdx].storage,
																   configs[configNdx].flags | extraFlags));
			}
		}
	}

	// .inter_call
	{
		tcu::TestCaseGroup* const withBarrierGroup		= new tcu::TestCaseGroup(m_testCtx, "with_memory_barrier", "Synchronize with memory barrier");
		tcu::TestCaseGroup* const withoutBarrierGroup	= new tcu::TestCaseGroup(m_testCtx, "without_memory_barrier", "Synchronize without memory barrier");

		interCallGroup->addChild(withBarrierGroup);
		interCallGroup->addChild(withoutBarrierGroup);

		// .with_memory_barrier
		{
			static const struct CaseConfig
			{
				const char*								namePrefix;
				const InterCallTestCase::StorageType	storage;
				const int								flags;
			} configs[] =
			{
				{ "image",			InterCallTestCase::STORAGE_IMAGE,	0																		},
				{ "image_atomic",	InterCallTestCase::STORAGE_IMAGE,	InterCallTestCase::FLAG_USE_ATOMIC | InterCallTestCase::FLAG_USE_INT	},
				{ "ssbo",			InterCallTestCase::STORAGE_BUFFER,	0																		},
				{ "ssbo_atomic",	InterCallTestCase::STORAGE_BUFFER,	InterCallTestCase::FLAG_USE_ATOMIC | InterCallTestCase::FLAG_USE_INT	},
			};

			const int seed0 = 123;
			const int seed1 = 457;

			for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
			{
				const char* const target = (configs[configNdx].storage == InterCallTestCase::STORAGE_BUFFER) ? ("buffer") : ("image");

				withBarrierGroup->addChild(new InterCallTestCase(m_context,
																 (std::string(configs[configNdx].namePrefix) + "_write_read").c_str(),
																 (std::string("Write to ") + target + " and read it").c_str(),
																 configs[configNdx].storage,
																 configs[configNdx].flags,
																 InterCallOperations()
																	<< op::WriteData::Generate(1, seed0)
																	<< op::Barrier()
																	<< op::ReadData::Generate(1, seed0)));

				withBarrierGroup->addChild(new InterCallTestCase(m_context,
																 (std::string(configs[configNdx].namePrefix) + "_read_write").c_str(),
																 (std::string("Read from ") + target + " and then write to it").c_str(),
																 configs[configNdx].storage,
																 configs[configNdx].flags,
																 InterCallOperations()
																	<< op::ReadZeroData::Generate(1)
																	<< op::Barrier()
																	<< op::WriteData::Generate(1, seed0)));

				withBarrierGroup->addChild(new InterCallTestCase(m_context,
																 (std::string(configs[configNdx].namePrefix) + "_overwrite").c_str(),
																 (std::string("Write to ") + target + " twice and read it").c_str(),
																 configs[configNdx].storage,
																 configs[configNdx].flags,
																 InterCallOperations()
																	<< op::WriteData::Generate(1, seed0)
																	<< op::Barrier()
																	<< op::WriteData::Generate(1, seed1)
																	<< op::Barrier()
																	<< op::ReadData::Generate(1, seed1)));

				withBarrierGroup->addChild(new InterCallTestCase(m_context,
																 (std::string(configs[configNdx].namePrefix) + "_multiple_write_read").c_str(),
																 (std::string("Write to multiple ") + target + "s and read them").c_str(),
																 configs[configNdx].storage,
																 configs[configNdx].flags,
																 InterCallOperations()
																	<< op::WriteData::Generate(1, seed0)
																	<< op::WriteData::Generate(2, seed1)
																	<< op::Barrier()
																	<< op::ReadMultipleData::Generate(1, seed0, 2, seed1)));

				withBarrierGroup->addChild(new InterCallTestCase(m_context,
																 (std::string(configs[configNdx].namePrefix) + "_multiple_interleaved_write_read").c_str(),
																 (std::string("Write to same ") + target + " in multiple calls and read it").c_str(),
																 configs[configNdx].storage,
																 configs[configNdx].flags,
																 InterCallOperations()
																	<< op::WriteDataInterleaved::Generate(1, seed0, true)
																	<< op::WriteDataInterleaved::Generate(1, seed1, false)
																	<< op::Barrier()
																	<< op::ReadDataInterleaved::Generate(1, seed0, seed1)));

				withBarrierGroup->addChild(new InterCallTestCase(m_context,
																 (std::string(configs[configNdx].namePrefix) + "_multiple_unrelated_write_read_ordered").c_str(),
																 (std::string("Two unrelated ") + target + " write-reads").c_str(),
																 configs[configNdx].storage,
																 configs[configNdx].flags,
																 InterCallOperations()
																	<< op::WriteData::Generate(1, seed0)
																	<< op::WriteData::Generate(2, seed1)
																	<< op::Barrier()
																	<< op::ReadData::Generate(1, seed0)
																	<< op::ReadData::Generate(2, seed1)));

				withBarrierGroup->addChild(new InterCallTestCase(m_context,
																 (std::string(configs[configNdx].namePrefix) + "_multiple_unrelated_write_read_non_ordered").c_str(),
																 (std::string("Two unrelated ") + target + " write-reads").c_str(),
																 configs[configNdx].storage,
																 configs[configNdx].flags,
																 InterCallOperations()
																	<< op::WriteData::Generate(1, seed0)
																	<< op::WriteData::Generate(2, seed1)
																	<< op::Barrier()
																	<< op::ReadData::Generate(2, seed1)
																	<< op::ReadData::Generate(1, seed0)));
			}

			// .without_memory_barrier
			{
				struct InvocationConfig
				{
					const char*	name;
					int			count;
				};

				static const InvocationConfig ssboInvocations[] =
				{
					{ "1k",		1024	},
					{ "4k",		4096	},
					{ "32k",	32768	},
				};
				static const InvocationConfig imageInvocations[] =
				{
					{ "8x8",		8	},
					{ "32x32",		32	},
					{ "128x128",	128	},
				};
				static const InvocationConfig counterInvocations[] =
				{
					{ "32",		32		},
					{ "128",	128		},
					{ "1k",		1024	},
				};
				static const int callCounts[] = { 2, 5, 100 };

				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(ssboInvocations); ++invocationNdx)
					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
						withoutBarrierGroup->addChild(new SSBOConcurrentAtomicCase(m_context, (std::string("ssbo_atomic_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + ssboInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], ssboInvocations[invocationNdx].count));

				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(imageInvocations); ++invocationNdx)
					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
						withoutBarrierGroup->addChild(new ConcurrentImageAtomicCase(m_context, (std::string("image_atomic_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + imageInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], imageInvocations[invocationNdx].count));

				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(counterInvocations); ++invocationNdx)
					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
						withoutBarrierGroup->addChild(new ConcurrentAtomicCounterCase(m_context, (std::string("atomic_counter_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + counterInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], counterInvocations[invocationNdx].count));

				for (int invocationNdx = 0; invocationNdx < DE_LENGTH_OF_ARRAY(counterInvocations); ++invocationNdx)
					for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); ++callCountNdx)
						withoutBarrierGroup->addChild(new ConcurrentSSBOAtomicCounterMixedCase(m_context, (std::string("ssbo_atomic_counter_mixed_dispatch_") + de::toString(callCounts[callCountNdx]) + "_calls_" + counterInvocations[invocationNdx].name + "_invocations").c_str(),	"", callCounts[callCountNdx], counterInvocations[invocationNdx].count));
			}
		}
	}
}

} // Functional
} // gles31
} // deqp