/*-------------------------------------------------------------------------
 * 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 Shader Image Load & Store Tests.
 *//*--------------------------------------------------------------------*/

#include "es31fShaderImageLoadStoreTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluProgramInterfaceQuery.hpp"
#include "gluDrawUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVector.hpp"
#include "tcuImageCompare.hpp"
#include "tcuFloat.hpp"
#include "tcuVectorUtil.hpp"
#include "deStringUtil.hpp"
#include "deSharedPtr.hpp"
#include "deUniquePtr.hpp"
#include "deRandom.hpp"
#include "deMemory.h"
#include "glwFunctions.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

#include <vector>
#include <string>
#include <algorithm>
#include <map>

using glu::RenderContext;
using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::UVec2;
using tcu::UVec3;
using tcu::UVec4;
using tcu::TextureFormat;
using tcu::ConstPixelBufferAccess;
using tcu::PixelBufferAccess;
using de::toString;
using de::SharedPtr;
using de::UniquePtr;

using std::vector;
using std::string;

namespace deqp
{

using namespace gls::TextureTestUtil;
using namespace glu::TextureTestUtil;

namespace gles31
{
namespace Functional
{

//! Default image sizes used in most test cases.
static inline IVec3 defaultImageSize (TextureType type)
{
	switch (type)
	{
		case TEXTURETYPE_BUFFER:	return IVec3(64,	1,		1);
		case TEXTURETYPE_2D:		return IVec3(64,	64,		1);
		case TEXTURETYPE_CUBE:		return IVec3(64,	64,		1);
		case TEXTURETYPE_3D:		return IVec3(64,	64,		8);
		case TEXTURETYPE_2D_ARRAY:	return IVec3(64,	64,		8);
		default:
			DE_ASSERT(false);
			return IVec3(-1);
	}
}

template <typename T, int Size>
static string arrayStr (const T (&arr)[Size])
{
	string result = "{ ";
	for (int i = 0; i < Size; i++)
		result += (i > 0 ? ", " : "") + toString(arr[i]);
	result += " }";
	return result;
}

template <typename T, int N>
static int arrayIndexOf (const T (&arr)[N], const T& e)
{
	return (int)(std::find(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr), e) - DE_ARRAY_BEGIN(arr));
}

static const char* getTextureTypeName (TextureType type)
{
	switch (type)
	{
		case TEXTURETYPE_BUFFER:	return "buffer";
		case TEXTURETYPE_2D:		return "2d";
		case TEXTURETYPE_CUBE:		return "cube";
		case TEXTURETYPE_3D:		return "3d";
		case TEXTURETYPE_2D_ARRAY:	return "2d_array";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

static inline bool isFormatTypeUnsignedInteger (TextureFormat::ChannelType type)
{
	return type == TextureFormat::UNSIGNED_INT8		||
		   type == TextureFormat::UNSIGNED_INT16	||
		   type == TextureFormat::UNSIGNED_INT32;
}

static inline bool isFormatTypeSignedInteger (TextureFormat::ChannelType type)
{
	return type == TextureFormat::SIGNED_INT8	||
		   type == TextureFormat::SIGNED_INT16	||
		   type == TextureFormat::SIGNED_INT32;
}

static inline bool isFormatTypeInteger (TextureFormat::ChannelType type)
{
	return isFormatTypeUnsignedInteger(type) || isFormatTypeSignedInteger(type);
}

static inline bool isFormatTypeUnorm (TextureFormat::ChannelType type)
{
	return type == TextureFormat::UNORM_INT8	||
		   type == TextureFormat::UNORM_INT16	||
		   type == TextureFormat::UNORM_INT32;
}

static inline bool isFormatTypeSnorm (TextureFormat::ChannelType type)
{
	return type == TextureFormat::SNORM_INT8	||
		   type == TextureFormat::SNORM_INT16	||
		   type == TextureFormat::SNORM_INT32;
}

static inline bool isFormatSupportedForTextureBuffer (const TextureFormat& format)
{
	switch (format.order)
	{
		case TextureFormat::RGB:
			return format.type == TextureFormat::FLOAT				||
				   format.type == TextureFormat::SIGNED_INT32		||
				   format.type == TextureFormat::UNSIGNED_INT32;

		// \note Fallthroughs.
		case TextureFormat::R:
		case TextureFormat::RG:
		case TextureFormat::RGBA:
			return format.type == TextureFormat::UNORM_INT8			||
				   format.type == TextureFormat::HALF_FLOAT			||
				   format.type == TextureFormat::FLOAT				||
				   format.type == TextureFormat::SIGNED_INT8		||
				   format.type == TextureFormat::SIGNED_INT16		||
				   format.type == TextureFormat::SIGNED_INT32		||
				   format.type == TextureFormat::UNSIGNED_INT8		||
				   format.type == TextureFormat::UNSIGNED_INT16		||
				   format.type == TextureFormat::UNSIGNED_INT32;

		default:
			return false;
	}
}

static inline string getShaderImageFormatQualifier (const TextureFormat& format)
{
	const char* orderPart;
	const char* typePart;

	switch (format.order)
	{
		case TextureFormat::R:		orderPart = "r";		break;
		case TextureFormat::RGBA:	orderPart = "rgba";		break;
		default:
			DE_ASSERT(false);
			orderPart = DE_NULL;
	}

	switch (format.type)
	{
		case TextureFormat::FLOAT:				typePart = "32f";			break;
		case TextureFormat::HALF_FLOAT:			typePart = "16f";			break;

		case TextureFormat::UNSIGNED_INT32:		typePart = "32ui";			break;
		case TextureFormat::UNSIGNED_INT16:		typePart = "16ui";			break;
		case TextureFormat::UNSIGNED_INT8:		typePart = "8ui";			break;

		case TextureFormat::SIGNED_INT32:		typePart = "32i";			break;
		case TextureFormat::SIGNED_INT16:		typePart = "16i";			break;
		case TextureFormat::SIGNED_INT8:		typePart = "8i";			break;

		case TextureFormat::UNORM_INT16:		typePart = "16";			break;
		case TextureFormat::UNORM_INT8:			typePart = "8";				break;

		case TextureFormat::SNORM_INT16:		typePart = "16_snorm";		break;
		case TextureFormat::SNORM_INT8:			typePart = "8_snorm";		break;

		default:
			DE_ASSERT(false);
			typePart = DE_NULL;
	}

	return string() + orderPart + typePart;
}

static inline string getShaderSamplerOrImageType (TextureFormat::ChannelType formatType, TextureType textureType, bool isSampler)
{
	const char* const formatPart		= isFormatTypeUnsignedInteger(formatType)	? "u"
										: isFormatTypeSignedInteger(formatType)		? "i"
										: "";

	const char* const imageTypePart		= textureType == TEXTURETYPE_BUFFER		? "Buffer"
										: textureType == TEXTURETYPE_2D			? "2D"
										: textureType == TEXTURETYPE_3D			? "3D"
										: textureType == TEXTURETYPE_CUBE		? "Cube"
										: textureType == TEXTURETYPE_2D_ARRAY	? "2DArray"
										: DE_NULL;

	return string() + formatPart + (isSampler ? "sampler" : "image") + imageTypePart;
}

static inline string getShaderImageType (TextureFormat::ChannelType formatType, TextureType imageType)
{
	return getShaderSamplerOrImageType(formatType, imageType, false);
}

static inline string getShaderSamplerType (TextureFormat::ChannelType formatType, TextureType imageType)
{
	return getShaderSamplerOrImageType(formatType, imageType, true);
}

static inline deUint32 getGLTextureTarget (TextureType texType)
{
	switch (texType)
	{
		case TEXTURETYPE_BUFFER:	return GL_TEXTURE_BUFFER;
		case TEXTURETYPE_2D:		return GL_TEXTURE_2D;
		case TEXTURETYPE_3D:		return GL_TEXTURE_3D;
		case TEXTURETYPE_CUBE:		return GL_TEXTURE_CUBE_MAP;
		case TEXTURETYPE_2D_ARRAY:	return GL_TEXTURE_2D_ARRAY;
		default:
			DE_ASSERT(false);
			return (deUint32)-1;
	}
}

static deUint32 cubeFaceToGLFace (tcu::CubeFace face)
{
	switch (face)
	{
		case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
		case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
		case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
		case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
		case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
		case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
		default:
			DE_ASSERT(false);
			return GL_NONE;
	}
}

static inline tcu::Texture1D* newOneLevelTexture1D (const tcu::TextureFormat& format, int w)
{
	tcu::Texture1D* const res = new tcu::Texture1D(format, w);
	res->allocLevel(0);
	return res;
}

static inline tcu::Texture2D* newOneLevelTexture2D (const tcu::TextureFormat& format, int w, int h)
{
	tcu::Texture2D* const res = new tcu::Texture2D(format, w, h);
	res->allocLevel(0);
	return res;
}

static inline tcu::TextureCube* newOneLevelTextureCube (const tcu::TextureFormat& format, int size)
{
	tcu::TextureCube* const res = new tcu::TextureCube(format, size);
	for (int i = 0; i < tcu::CUBEFACE_LAST; i++)
		res->allocLevel((tcu::CubeFace)i, 0);
	return res;
}

static inline tcu::Texture3D* newOneLevelTexture3D (const tcu::TextureFormat& format, int w, int h, int d)
{
	tcu::Texture3D* const res = new tcu::Texture3D(format, w, h, d);
	res->allocLevel(0);
	return res;
}

static inline tcu::Texture2DArray* newOneLevelTexture2DArray (const tcu::TextureFormat& format, int w, int h, int d)
{
	tcu::Texture2DArray* const res = new tcu::Texture2DArray(format, w, h, d);
	res->allocLevel(0);
	return res;
}

static inline TextureType textureLayerType (TextureType entireTextureType)
{
	switch (entireTextureType)
	{
		// Single-layer types.
		// \note Fallthrough.
		case TEXTURETYPE_BUFFER:
		case TEXTURETYPE_2D:
			return entireTextureType;

		// Multi-layer types with 2d layers.
		case TEXTURETYPE_3D:
		case TEXTURETYPE_CUBE:
		case TEXTURETYPE_2D_ARRAY:
			return TEXTURETYPE_2D;

		default:
			DE_ASSERT(false);
			return TEXTURETYPE_LAST;
	}
}

static const char* const s_texBufExtString = "GL_EXT_texture_buffer";

static inline void checkTextureTypeExtensions (const glu::ContextInfo& contextInfo, TextureType type, const RenderContext& renderContext)
{
	if (type == TEXTURETYPE_BUFFER && !contextInfo.isExtensionSupported(s_texBufExtString) && !glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)))
		throw tcu::NotSupportedError("Test requires " + string(s_texBufExtString) + " extension");
}

static inline string textureTypeExtensionShaderRequires (TextureType type, const RenderContext& renderContext)
{
	if (!glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) && (type == TEXTURETYPE_BUFFER))
		return "#extension " + string(s_texBufExtString) + " : require\n";
	else
		return "";
}

static const char* const s_imageAtomicExtString = "GL_OES_shader_image_atomic";

static inline string imageAtomicExtensionShaderRequires (const RenderContext& renderContext)
{
	if (!glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)))
		return "#extension " + string(s_imageAtomicExtString) + " : require\n";
	else
		return "";
}

namespace
{

enum AtomicOperation
{
	ATOMIC_OPERATION_ADD = 0,
	ATOMIC_OPERATION_MIN,
	ATOMIC_OPERATION_MAX,
	ATOMIC_OPERATION_AND,
	ATOMIC_OPERATION_OR,
	ATOMIC_OPERATION_XOR,
	ATOMIC_OPERATION_EXCHANGE,
	ATOMIC_OPERATION_COMP_SWAP,

	ATOMIC_OPERATION_LAST
};

//! An order-independent operation is one for which the end result doesn't depend on the order in which the operations are carried (i.e. is both commutative and associative).
static bool isOrderIndependentAtomicOperation (AtomicOperation op)
{
	return op == ATOMIC_OPERATION_ADD	||
		   op == ATOMIC_OPERATION_MIN	||
		   op == ATOMIC_OPERATION_MAX	||
		   op == ATOMIC_OPERATION_AND	||
		   op == ATOMIC_OPERATION_OR	||
		   op == ATOMIC_OPERATION_XOR;
}

//! Computes the result of an atomic operation where "a" is the data operated on and "b" is the parameter to the atomic function.
int computeBinaryAtomicOperationResult (AtomicOperation op, int a, int b)
{
	switch (op)
	{
		case ATOMIC_OPERATION_ADD:			return a + b;
		case ATOMIC_OPERATION_MIN:			return de::min(a, b);
		case ATOMIC_OPERATION_MAX:			return de::max(a, b);
		case ATOMIC_OPERATION_AND:			return a & b;
		case ATOMIC_OPERATION_OR:			return a | b;
		case ATOMIC_OPERATION_XOR:			return a ^ b;
		case ATOMIC_OPERATION_EXCHANGE:		return b;
		default:
			DE_ASSERT(false);
			return -1;
	}
}

//! \note For floats, only the exchange operation is supported.
float computeBinaryAtomicOperationResult (AtomicOperation op, float /*a*/, float b)
{
	switch (op)
	{
		case ATOMIC_OPERATION_EXCHANGE: return b;
		default:
			DE_ASSERT(false);
			return -1;
	}
}

static const char* getAtomicOperationCaseName (AtomicOperation op)
{
	switch (op)
	{
		case ATOMIC_OPERATION_ADD:			return "add";
		case ATOMIC_OPERATION_MIN:			return "min";
		case ATOMIC_OPERATION_MAX:			return "max";
		case ATOMIC_OPERATION_AND:			return "and";
		case ATOMIC_OPERATION_OR:			return "or";
		case ATOMIC_OPERATION_XOR:			return "xor";
		case ATOMIC_OPERATION_EXCHANGE:		return "exchange";
		case ATOMIC_OPERATION_COMP_SWAP:	return "comp_swap";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

static const char* getAtomicOperationShaderFuncName (AtomicOperation op)
{
	switch (op)
	{
		case ATOMIC_OPERATION_ADD:			return "imageAtomicAdd";
		case ATOMIC_OPERATION_MIN:			return "imageAtomicMin";
		case ATOMIC_OPERATION_MAX:			return "imageAtomicMax";
		case ATOMIC_OPERATION_AND:			return "imageAtomicAnd";
		case ATOMIC_OPERATION_OR:			return "imageAtomicOr";
		case ATOMIC_OPERATION_XOR:			return "imageAtomicXor";
		case ATOMIC_OPERATION_EXCHANGE:		return "imageAtomicExchange";
		case ATOMIC_OPERATION_COMP_SWAP:	return "imageAtomicCompSwap";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

//! In GLSL, when accessing cube images, the z coordinate is mapped to a cube face.
//! \note This is _not_ the same as casting the z to a tcu::CubeFace.
static inline tcu::CubeFace glslImageFuncZToCubeFace (int z)
{
	static const tcu::CubeFace faces[6] =
	{
		tcu::CUBEFACE_POSITIVE_X,
		tcu::CUBEFACE_NEGATIVE_X,
		tcu::CUBEFACE_POSITIVE_Y,
		tcu::CUBEFACE_NEGATIVE_Y,
		tcu::CUBEFACE_POSITIVE_Z,
		tcu::CUBEFACE_NEGATIVE_Z
	};

	DE_ASSERT(de::inBounds(z, 0, DE_LENGTH_OF_ARRAY(faces)));
	return faces[z];
}

class BufferMemMap
{
public:
	BufferMemMap (const glw::Functions& gl, deUint32 target, int offset, int size, deUint32 access)
		: m_gl		(gl)
		, m_target	(target)
		, m_ptr		(DE_NULL)
	{
		m_ptr = gl.mapBufferRange(target, offset, size, access);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
		TCU_CHECK(m_ptr);
	}

	~BufferMemMap (void)
	{
		m_gl.unmapBuffer(m_target);
	}

	void*	getPtr		(void) const { return m_ptr; }
	void*	operator*	(void) const { return m_ptr; }

private:
							BufferMemMap			(const BufferMemMap& other);
	BufferMemMap&			operator=				(const BufferMemMap& other);

	const glw::Functions&	m_gl;
	const deUint32			m_target;
	void*					m_ptr;
};

//! Utility for more readable uniform assignment logging; logs the name of the uniform when assigning. Handles the locations, querying them the first time they're assigned
//  \note Assumes that the appropriate program is in use when assigning uniforms.
class UniformAccessLogger
{
public:
	UniformAccessLogger (const glw::Functions& gl, TestLog& log, deUint32 programGL)
		: m_gl			(gl)
		, m_log			(log)
		, m_programGL	(programGL)
	{
	}

	void						assign1i (const string& name, int x);
	void						assign3f (const string& name, float x, float y, float z);

private:
	int							getLocation (const string& name);

	const glw::Functions&		m_gl;
	TestLog&					m_log;
	const deUint32				m_programGL;

	std::map<string, int>		m_uniformLocations;
};

int UniformAccessLogger::getLocation (const string& name)
{
	if (m_uniformLocations.find(name) == m_uniformLocations.end())
	{
		const int loc = m_gl.getUniformLocation(m_programGL, name.c_str());
		TCU_CHECK(loc != -1);
		m_uniformLocations[name] = loc;
	}
	return m_uniformLocations[name];
}

void UniformAccessLogger::assign1i (const string& name, int x)
{
	const int loc = getLocation(name);
	m_log << TestLog::Message << "// Assigning to uniform " << name << ": " << x << TestLog::EndMessage;
	m_gl.uniform1i(loc, x);
}

void UniformAccessLogger::assign3f (const string& name, float x, float y, float z)
{
	const int loc = getLocation(name);
	m_log << TestLog::Message << "// Assigning to uniform " << name << ": " << Vec3(x, y, z) << TestLog::EndMessage;
	m_gl.uniform3f(loc, x, y, z);
}

//! Class containing a (single-level) texture of a given type. Supports accessing pixels with coordinate convention similar to that in imageStore() and imageLoad() in shaders; useful especially for cube maps.
class LayeredImage
{
public:
												LayeredImage				(TextureType type, const TextureFormat& format, int w, int h, int d);

	TextureType									getImageType				(void) const { return m_type; }
	const IVec3&								getSize						(void) const { return m_size; }
	const TextureFormat&						getFormat					(void) const { return m_format; }

	// \note For cube maps, set/getPixel's z parameter specifies the cube face in the same manner as in imageStore/imageLoad in GL shaders (see glslImageFuncZToCubeFace), instead of directly as a tcu::CubeFace.

	template <typename ColorT>
	void										setPixel					(int x, int y, int z, const ColorT& color) const;

	Vec4										getPixel					(int x, int y, int z) const;
	IVec4										getPixelInt					(int x, int y, int z) const;
	UVec4										getPixelUint				(int x, int y, int z) const { return getPixelInt(x, y, z).asUint(); }

	PixelBufferAccess							getAccess					(void)							{ return getAccessInternal();				}
	PixelBufferAccess							getSliceAccess				(int slice)						{ return getSliceAccessInternal(slice);		}
	PixelBufferAccess							getCubeFaceAccess			(tcu::CubeFace face)			{ return getCubeFaceAccessInternal(face);	}

	ConstPixelBufferAccess						getAccess					(void)					const	{ return getAccessInternal();				}
	ConstPixelBufferAccess						getSliceAccess				(int slice)				const	{ return getSliceAccessInternal(slice);		}
	ConstPixelBufferAccess						getCubeFaceAccess			(tcu::CubeFace face)	const	{ return getCubeFaceAccessInternal(face);	}

private:
												LayeredImage				(const LayeredImage&);
	LayeredImage&								operator=					(const LayeredImage&);

	// Some helpers to reduce code duplication between const/non-const versions of getAccess and others.
	PixelBufferAccess							getAccessInternal			(void) const;
	PixelBufferAccess							getSliceAccessInternal		(int slice) const;
	PixelBufferAccess							getCubeFaceAccessInternal	(tcu::CubeFace face) const;

	const TextureType							m_type;
	const IVec3									m_size;
	const TextureFormat							m_format;

	// \note Depending on m_type, exactly one of the following will contain non-null.
	const SharedPtr<tcu::Texture1D>				m_texBuffer;
	const SharedPtr<tcu::Texture2D>				m_tex2D;
	const SharedPtr<tcu::TextureCube>			m_texCube;
	const SharedPtr<tcu::Texture3D>				m_tex3D;
	const SharedPtr<tcu::Texture2DArray>		m_tex2DArray;
};

LayeredImage::LayeredImage (TextureType type, const TextureFormat& format, int w, int h, int d)
	: m_type		(type)
	, m_size		(w, h, d)
	, m_format		(format)
	, m_texBuffer	(type == TEXTURETYPE_BUFFER		? SharedPtr<tcu::Texture1D>			(newOneLevelTexture1D		(format, w))		: SharedPtr<tcu::Texture1D>())
	, m_tex2D		(type == TEXTURETYPE_2D			? SharedPtr<tcu::Texture2D>			(newOneLevelTexture2D		(format, w, h))		: SharedPtr<tcu::Texture2D>())
	, m_texCube		(type == TEXTURETYPE_CUBE		? SharedPtr<tcu::TextureCube>		(newOneLevelTextureCube		(format, w))		: SharedPtr<tcu::TextureCube>())
	, m_tex3D		(type == TEXTURETYPE_3D			? SharedPtr<tcu::Texture3D>			(newOneLevelTexture3D		(format, w, h, d))	: SharedPtr<tcu::Texture3D>())
	, m_tex2DArray	(type == TEXTURETYPE_2D_ARRAY	? SharedPtr<tcu::Texture2DArray>	(newOneLevelTexture2DArray	(format, w, h, d))	: SharedPtr<tcu::Texture2DArray>())
{
	DE_ASSERT(m_size.z() == 1					||
			  m_type == TEXTURETYPE_3D			||
			  m_type == TEXTURETYPE_2D_ARRAY);

	DE_ASSERT(m_size.y() == 1					||
			  m_type == TEXTURETYPE_2D			||
			  m_type == TEXTURETYPE_CUBE		||
			  m_type == TEXTURETYPE_3D			||
			  m_type == TEXTURETYPE_2D_ARRAY);

	DE_ASSERT(w == h || type != TEXTURETYPE_CUBE);

	DE_ASSERT(m_texBuffer	!= DE_NULL ||
			  m_tex2D		!= DE_NULL ||
			  m_texCube		!= DE_NULL ||
			  m_tex3D		!= DE_NULL ||
			  m_tex2DArray	!= DE_NULL);
}

template <typename ColorT>
void LayeredImage::setPixel (int x, int y, int z, const ColorT& color) const
{
	const PixelBufferAccess access = m_type == TEXTURETYPE_BUFFER		? m_texBuffer->getLevel(0)
								   : m_type == TEXTURETYPE_2D			? m_tex2D->getLevel(0)
								   : m_type == TEXTURETYPE_CUBE			? m_texCube->getLevelFace(0, glslImageFuncZToCubeFace(z))
								   : m_type == TEXTURETYPE_3D			? m_tex3D->getLevel(0)
								   : m_type == TEXTURETYPE_2D_ARRAY		? m_tex2DArray->getLevel(0)
								   : PixelBufferAccess();

	access.setPixel(color, x, y, m_type == TEXTURETYPE_CUBE ? 0 : z);
}

Vec4 LayeredImage::getPixel (int x, int y, int z) const
{
	const ConstPixelBufferAccess access = m_type == TEXTURETYPE_CUBE ? getCubeFaceAccess(glslImageFuncZToCubeFace(z)) : getAccess();
	return access.getPixel(x, y, m_type == TEXTURETYPE_CUBE ? 0 : z);
}

IVec4 LayeredImage::getPixelInt (int x, int y, int z) const
{
	const ConstPixelBufferAccess access = m_type == TEXTURETYPE_CUBE ? getCubeFaceAccess(glslImageFuncZToCubeFace(z)) : getAccess();
	return access.getPixelInt(x, y, m_type == TEXTURETYPE_CUBE ? 0 : z);
}

PixelBufferAccess LayeredImage::getAccessInternal (void) const
{
	DE_ASSERT(m_type == TEXTURETYPE_BUFFER || m_type == TEXTURETYPE_2D || m_type == TEXTURETYPE_3D || m_type == TEXTURETYPE_2D_ARRAY);

	return m_type == TEXTURETYPE_BUFFER		? m_texBuffer->getLevel(0)
		 : m_type == TEXTURETYPE_2D			? m_tex2D->getLevel(0)
		 : m_type == TEXTURETYPE_3D			? m_tex3D->getLevel(0)
		 : m_type == TEXTURETYPE_2D_ARRAY	? m_tex2DArray->getLevel(0)
		 : PixelBufferAccess();
}

PixelBufferAccess LayeredImage::getSliceAccessInternal (int slice) const
{
	const PixelBufferAccess srcAccess = getAccessInternal();
	return tcu::getSubregion(srcAccess, 0, 0, slice, srcAccess.getWidth(), srcAccess.getHeight(), 1);
}

PixelBufferAccess LayeredImage::getCubeFaceAccessInternal (tcu::CubeFace face) const
{
	DE_ASSERT(m_type == TEXTURETYPE_CUBE);
	return m_texCube->getLevelFace(0, face);
}

//! Set texture storage or, if using buffer texture, setup buffer and attach to texture.
static void setTextureStorage (glu::CallLogWrapper& glLog, TextureType imageType, deUint32 internalFormat, const IVec3& imageSize, deUint32 textureBufGL)
{
	const deUint32 textureTarget = getGLTextureTarget(imageType);

	switch (imageType)
	{
		case TEXTURETYPE_BUFFER:
		{
			const TextureFormat		format		= glu::mapGLInternalFormat(internalFormat);
			const int				numBytes	= format.getPixelSize() * imageSize.x();
			DE_ASSERT(isFormatSupportedForTextureBuffer(format));
			glLog.glBindBuffer(GL_TEXTURE_BUFFER, textureBufGL);
			glLog.glBufferData(GL_TEXTURE_BUFFER, numBytes, DE_NULL, GL_STATIC_DRAW);
			glLog.glTexBuffer(GL_TEXTURE_BUFFER, internalFormat, textureBufGL);
			DE_ASSERT(imageSize.y() == 1 && imageSize.z() == 1);
			break;
		}

		// \note Fall-throughs.

		case TEXTURETYPE_2D:
		case TEXTURETYPE_CUBE:
			glLog.glTexStorage2D(textureTarget, 1, internalFormat, imageSize.x(), imageSize.y());
			DE_ASSERT(imageSize.z() == 1);
			break;

		case TEXTURETYPE_3D:
		case TEXTURETYPE_2D_ARRAY:
			glLog.glTexStorage3D(textureTarget, 1, internalFormat, imageSize.x(), imageSize.y(), imageSize.z());
			break;

		default:
			DE_ASSERT(false);
	}
}

static void uploadTexture (glu::CallLogWrapper& glLog, const LayeredImage& src, deUint32 textureBufGL)
{
	const deUint32				internalFormat	= glu::getInternalFormat(src.getFormat());
	const glu::TransferFormat	transferFormat	= glu::getTransferFormat(src.getFormat());
	const IVec3&				imageSize		= src.getSize();

	setTextureStorage(glLog, src.getImageType(), internalFormat, imageSize, textureBufGL);

	{
		const int	pixelSize = src.getFormat().getPixelSize();
		int			unpackAlignment;

		if (deIsPowerOfTwo32(pixelSize))
			unpackAlignment = 8;
		else
			unpackAlignment = 1;

		glLog.glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlignment);
	}

	if (src.getImageType() == TEXTURETYPE_BUFFER)
	{
		glLog.glBindBuffer(GL_TEXTURE_BUFFER, textureBufGL);
		glLog.glBufferData(GL_TEXTURE_BUFFER, src.getFormat().getPixelSize() * imageSize.x(), src.getAccess().getDataPtr(), GL_STATIC_DRAW);
	}
	else if (src.getImageType() == TEXTURETYPE_2D)
		glLog.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imageSize.x(), imageSize.y(), transferFormat.format, transferFormat.dataType, src.getAccess().getDataPtr());
	else if (src.getImageType() == TEXTURETYPE_CUBE)
	{
		for (int faceI = 0; faceI < tcu::CUBEFACE_LAST; faceI++)
		{
			const tcu::CubeFace face = (tcu::CubeFace)faceI;
			glLog.glTexSubImage2D(cubeFaceToGLFace(face), 0, 0, 0, imageSize.x(), imageSize.y(), transferFormat.format, transferFormat.dataType, src.getCubeFaceAccess(face).getDataPtr());
		}
	}
	else
	{
		DE_ASSERT(src.getImageType() == TEXTURETYPE_3D || src.getImageType() == TEXTURETYPE_2D_ARRAY);
		const deUint32 textureTarget = getGLTextureTarget(src.getImageType());
		glLog.glTexSubImage3D(textureTarget, 0, 0, 0, 0, imageSize.x(), imageSize.y(), imageSize.z(), transferFormat.format, transferFormat.dataType, src.getAccess().getDataPtr());
	}
}

static void readPixelsRGBAInteger32 (const PixelBufferAccess& dst, int originX, int originY, glu::CallLogWrapper& glLog)
{
	DE_ASSERT(dst.getDepth() == 1);

	if (isFormatTypeUnsignedInteger(dst.getFormat().type))
	{
		vector<UVec4> data(dst.getWidth()*dst.getHeight());

		glLog.glReadPixels(originX, originY, dst.getWidth(), dst.getHeight(), GL_RGBA_INTEGER, GL_UNSIGNED_INT, &data[0]);

		for (int y = 0; y < dst.getHeight(); y++)
		for (int x = 0; x < dst.getWidth(); x++)
			dst.setPixel(data[y*dst.getWidth() + x], x, y);
	}
	else if (isFormatTypeSignedInteger(dst.getFormat().type))
	{
		vector<IVec4> data(dst.getWidth()*dst.getHeight());

		glLog.glReadPixels(originX, originY, dst.getWidth(), dst.getHeight(), GL_RGBA_INTEGER, GL_INT, &data[0]);

		for (int y = 0; y < dst.getHeight(); y++)
		for (int x = 0; x < dst.getWidth(); x++)
			dst.setPixel(data[y*dst.getWidth() + x], x, y);
	}
	else
		DE_ASSERT(false);
}

//! Base for a functor for verifying and logging a 2d texture layer (2d image, cube face, 3d slice, 2d layer).
class ImageLayerVerifier
{
public:
	virtual bool	operator()				(TestLog&, const ConstPixelBufferAccess&, int sliceOrFaceNdx) const = 0;
	virtual			~ImageLayerVerifier		(void) {}
};

static void setTexParameteri (glu::CallLogWrapper& glLog, deUint32 target)
{
	if (target != GL_TEXTURE_BUFFER)
	{
		glLog.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glLog.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}
}

//! Binds texture (one layer at a time) to color attachment of FBO and does glReadPixels(). Calls the verifier for each layer.
//! \note Not for buffer textures.
static bool readIntegerTextureViaFBOAndVerify (const RenderContext&			renderCtx,
											   glu::CallLogWrapper&			glLog,
											   deUint32						textureGL,
											   TextureType					textureType,
											   const TextureFormat&			textureFormat,
											   const IVec3&					textureSize,
											   const ImageLayerVerifier&	verifyLayer)
{
	DE_ASSERT(isFormatTypeInteger(textureFormat.type));
	DE_ASSERT(textureType != TEXTURETYPE_BUFFER);

	TestLog& log = glLog.getLog();

	const tcu::ScopedLogSection section(log, "Verification", "Result verification (bind texture layer-by-layer to FBO, read with glReadPixels())");

	const int			numSlicesOrFaces	= textureType == TEXTURETYPE_CUBE ? 6 : textureSize.z();
	const deUint32		textureTargetGL		= getGLTextureTarget(textureType);
	glu::Framebuffer	fbo					(renderCtx);
	tcu::TextureLevel	resultSlice			(textureFormat, textureSize.x(), textureSize.y());

	glLog.glBindFramebuffer(GL_FRAMEBUFFER, *fbo);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Bind FBO");

	glLog.glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glMemoryBarrier");

	glLog.glActiveTexture(GL_TEXTURE0);
	glLog.glBindTexture(textureTargetGL, textureGL);
	setTexParameteri(glLog, textureTargetGL);

	for (int sliceOrFaceNdx = 0; sliceOrFaceNdx < numSlicesOrFaces; sliceOrFaceNdx++)
	{
		if (textureType == TEXTURETYPE_CUBE)
			glLog.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)), textureGL, 0);
		else if (textureType == TEXTURETYPE_2D)
			glLog.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureGL, 0);
		else if (textureType == TEXTURETYPE_3D || textureType == TEXTURETYPE_2D_ARRAY)
			glLog.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureGL, 0, sliceOrFaceNdx);
		else
			DE_ASSERT(false);

		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Bind texture to framebuffer color attachment 0");

		TCU_CHECK(glLog.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

		readPixelsRGBAInteger32(resultSlice.getAccess(), 0, 0, glLog);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glReadPixels");

		if (!verifyLayer(log, resultSlice, sliceOrFaceNdx))
			return false;
	}

	return true;
}

//! Reads texture with texture() in compute shader, one layer at a time, putting values into a SSBO and reading with a mapping. Calls the verifier for each layer.
//! \note Not for buffer textures.
static bool readFloatOrNormTextureWithLookupsAndVerify (const RenderContext&		renderCtx,
														glu::CallLogWrapper&		glLog,
														deUint32					textureGL,
														TextureType					textureType,
														const TextureFormat&		textureFormat,
														const IVec3&				textureSize,
														const ImageLayerVerifier&	verifyLayer)
{
	DE_ASSERT(!isFormatTypeInteger(textureFormat.type));
	DE_ASSERT(textureType != TEXTURETYPE_BUFFER);

	TestLog& log = glLog.getLog();

	const tcu::ScopedLogSection section(log, "Verification", "Result verification (read texture layer-by-layer in compute shader with texture() into SSBO)");
	const std::string			glslVersionDeclaration = getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));

	const glu::ShaderProgram program(renderCtx,
		glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n"
													"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
													"layout (binding = 0) buffer Output\n"
													"{\n"
													"	vec4 color[" + toString(textureSize.x()*textureSize.y()) + "];\n"
													"} sb_out;\n"
													"\n"
													"precision highp " + getShaderSamplerType(textureFormat.type, textureType) + ";\n"
													"\n"
													"uniform highp " + getShaderSamplerType(textureFormat.type, textureType) + " u_texture;\n"
													"uniform highp vec3 u_texCoordLD;\n"
													"uniform highp vec3 u_texCoordRD;\n"
													"uniform highp vec3 u_texCoordLU;\n"
													"uniform highp vec3 u_texCoordRU;\n"
													"\n"
													"void main (void)\n"
													"{\n"
													"	int gx = int(gl_GlobalInvocationID.x);\n"
													"	int gy = int(gl_GlobalInvocationID.y);\n"
													"	highp float s = (float(gx) + 0.5) / float(" + toString(textureSize.x()) + ");\n"
													"	highp float t = (float(gy) + 0.5) / float(" + toString(textureType == TEXTURETYPE_CUBE ? textureSize.x() : textureSize.y()) + ");\n"
													"	highp vec3 texCoord = u_texCoordLD*(1.0-s)*(1.0-t)\n"
													"	                    + u_texCoordRD*(    s)*(1.0-t)\n"
													"	                    + u_texCoordLU*(1.0-s)*(    t)\n"
													"	                    + u_texCoordRU*(    s)*(    t);\n"
													"	int ndx = gy*" + toString(textureSize.x()) + " + gx;\n"
													"	sb_out.color[ndx] = texture(u_texture, texCoord" + (textureType == TEXTURETYPE_2D ? ".xy" : "") + ");\n"
													"}\n"));

	glLog.glUseProgram(program.getProgram());

	log << program;

	if (!program.isOk())
	{
		log << TestLog::Message << "// Failure: failed to compile program" << TestLog::EndMessage;
		TCU_FAIL("Program compilation failed");
	}

	{
		const deUint32			textureTargetGL		= getGLTextureTarget(textureType);
		const glu::Buffer		outputBuffer		(renderCtx);
		UniformAccessLogger		uniforms			(renderCtx.getFunctions(), log, program.getProgram());

		// Setup texture.

		glLog.glActiveTexture(GL_TEXTURE0);
		glLog.glBindTexture(textureTargetGL, textureGL);
		setTexParameteri(glLog, textureTargetGL);

		uniforms.assign1i("u_texture", 0);

		// Setup output buffer.
		{
			const deUint32		blockIndex		= glLog.glGetProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= glu::getProgramResourceInt(renderCtx.getFunctions(), program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			log << TestLog::Message << "// Got buffer data size = " << blockSize << TestLog::EndMessage;
			TCU_CHECK(blockSize > 0);

			glLog.glBindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			glLog.glBufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
			glLog.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "SSB setup failed");
		}

		// Dispatch one layer at a time, read back and verify.
		{
			const int							numSlicesOrFaces	= textureType == TEXTURETYPE_CUBE ? 6 : textureSize.z();
			tcu::TextureLevel					resultSlice			(textureFormat, textureSize.x(), textureSize.y());
			const PixelBufferAccess				resultSliceAccess	= resultSlice.getAccess();
			const deUint32						blockIndex			= glLog.glGetProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int							blockSize			= glu::getProgramResourceInt(renderCtx.getFunctions(), program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32						valueIndex			= glLog.glGetProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.color");
			const glu::InterfaceVariableInfo	valueInfo			= glu::getProgramInterfaceVariableInfo(renderCtx.getFunctions(), program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);

			TCU_CHECK(valueInfo.arraySize == (deUint32)(textureSize.x()*textureSize.y()));

			glLog.glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);

			for (int sliceOrFaceNdx = 0; sliceOrFaceNdx < numSlicesOrFaces; sliceOrFaceNdx++)
			{
				if (textureType == TEXTURETYPE_CUBE)
				{
					vector<float> coords;
					computeQuadTexCoordCube(coords, glslImageFuncZToCubeFace(sliceOrFaceNdx));
					uniforms.assign3f("u_texCoordLD", coords[3*0 + 0], coords[3*0 + 1], coords[3*0 + 2]);
					uniforms.assign3f("u_texCoordRD", coords[3*2 + 0], coords[3*2 + 1], coords[3*2 + 2]);
					uniforms.assign3f("u_texCoordLU", coords[3*1 + 0], coords[3*1 + 1], coords[3*1 + 2]);
					uniforms.assign3f("u_texCoordRU", coords[3*3 + 0], coords[3*3 + 1], coords[3*3 + 2]);
				}
				else
				{
					const float z = textureType == TEXTURETYPE_3D ?
										((float)sliceOrFaceNdx + 0.5f) / (float)numSlicesOrFaces :
										(float)sliceOrFaceNdx;
					uniforms.assign3f("u_texCoordLD", 0.0f, 0.0f, z);
					uniforms.assign3f("u_texCoordRD", 1.0f, 0.0f, z);
					uniforms.assign3f("u_texCoordLU", 0.0f, 1.0f, z);
					uniforms.assign3f("u_texCoordRU", 1.0f, 1.0f, z);
				}

				glLog.glDispatchCompute(textureSize.x(), textureSize.y(), 1);

				{
					log << TestLog::Message << "// Note: mapping buffer and reading color values written" << TestLog::EndMessage;

					const BufferMemMap bufMap(renderCtx.getFunctions(), GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

					for (int y = 0; y < textureSize.y(); y++)
					for (int x = 0; x < textureSize.x(); x++)
					{
						const int				ndx			= y*textureSize.x() + x;
						const float* const		clrData		= (const float*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx);

						switch (textureFormat.order)
						{
							case TextureFormat::R:		resultSliceAccess.setPixel(Vec4(clrData[0]),											x, y); break;
							case TextureFormat::RGBA:	resultSliceAccess.setPixel(Vec4(clrData[0], clrData[1], clrData[2], clrData[3]),		x, y); break;
							default:
								DE_ASSERT(false);
						}
					}
				}

				if (!verifyLayer(log, resultSliceAccess, sliceOrFaceNdx))
					return false;
			}
		}

		return true;
	}
}

//! Read buffer texture by reading the corresponding buffer with a mapping.
static bool readBufferTextureWithMappingAndVerify (const RenderContext&			renderCtx,
												   glu::CallLogWrapper&			glLog,
												   deUint32						bufferGL,
												   const TextureFormat&			textureFormat,
												   int							imageSize,
												   const ImageLayerVerifier&	verifyLayer)
{
	tcu::TextureLevel			result			(textureFormat, imageSize, 1);
	const PixelBufferAccess		resultAccess	= result.getAccess();
	const int					dataSize		= imageSize * textureFormat.getPixelSize();

	const tcu::ScopedLogSection section(glLog.getLog(), "Verification", "Result verification (read texture's buffer with a mapping)");
	glLog.glBindBuffer(GL_TEXTURE_BUFFER, bufferGL);

	{
		const BufferMemMap bufMap(renderCtx.getFunctions(), GL_TEXTURE_BUFFER, 0, dataSize, GL_MAP_READ_BIT);
		deMemcpy(resultAccess.getDataPtr(), bufMap.getPtr(), dataSize);
	}

	return verifyLayer(glLog.getLog(), resultAccess, 0);
}

//! Calls the appropriate texture verification function depending on texture format or type.
static bool readTextureAndVerify (const RenderContext&			renderCtx,
								  glu::CallLogWrapper&			glLog,
								  deUint32						textureGL,
								  deUint32						bufferGL,
								  TextureType					textureType,
								  const TextureFormat&			textureFormat,
								  const IVec3&					imageSize,
								  const ImageLayerVerifier&		verifyLayer)
{
	if (textureType == TEXTURETYPE_BUFFER)
		return readBufferTextureWithMappingAndVerify(renderCtx, glLog, bufferGL, textureFormat, imageSize.x(), verifyLayer);
	else
		return isFormatTypeInteger(textureFormat.type) ? readIntegerTextureViaFBOAndVerify				(renderCtx, glLog, textureGL, textureType, textureFormat, imageSize, verifyLayer)
													   : readFloatOrNormTextureWithLookupsAndVerify		(renderCtx, glLog, textureGL, textureType, textureFormat, imageSize, verifyLayer);
}

//! An ImageLayerVerifier that simply compares the result slice to a slice in a reference image.
//! \note Holds the reference image as a reference (no pun intended) instead of a copy; caller must be aware of lifetime issues.
class ImageLayerComparer : public ImageLayerVerifier
{
public:
	ImageLayerComparer (const LayeredImage& reference,
						const IVec2& relevantRegion = IVec2(0) /* If given, only check this region of each slice. */)
		: m_reference		(reference)
		, m_relevantRegion	(relevantRegion.x() > 0 && relevantRegion.y() > 0 ? relevantRegion : reference.getSize().swizzle(0, 1))
	{
	}

	bool operator() (TestLog& log, const tcu::ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
	{
		const bool						isCube				= m_reference.getImageType() == TEXTURETYPE_CUBE;
		const ConstPixelBufferAccess	referenceSlice		= tcu::getSubregion(isCube ? m_reference.getCubeFaceAccess(glslImageFuncZToCubeFace(sliceOrFaceNdx))
																					   : m_reference.getSliceAccess(sliceOrFaceNdx),
																				0, 0, m_relevantRegion.x(), m_relevantRegion.y());

		const string comparisonName = "Comparison" + toString(sliceOrFaceNdx);
		const string comparisonDesc = "Image Comparison, "
									+ (isCube ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
											  : "slice " + toString(sliceOrFaceNdx));

		if (isFormatTypeInteger(m_reference.getFormat().type))
			return tcu::intThresholdCompare(log, comparisonName.c_str(), comparisonDesc.c_str(), referenceSlice, resultSlice, UVec4(0), tcu::COMPARE_LOG_RESULT);
		else
			return tcu::floatThresholdCompare(log, comparisonName.c_str(), comparisonDesc.c_str(), referenceSlice, resultSlice, Vec4(0.01f), tcu::COMPARE_LOG_RESULT);
	}

private:
	const LayeredImage&		m_reference;
	const IVec2				m_relevantRegion;
};

//! Case that just stores some computation results into an image.
class ImageStoreCase : public TestCase
{
public:
	enum CaseFlag
	{
		CASEFLAG_SINGLE_LAYER_BIND = 1 << 0 //!< If given, glBindImageTexture() is called with GL_FALSE <layered> argument, and for each layer the compute shader is separately dispatched.
	};

	ImageStoreCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType textureType, deUint32 caseFlags = 0)
		: TestCase				(context, name, description)
		, m_format				(format)
		, m_textureType			(textureType)
		, m_singleLayerBind		((caseFlags & CASEFLAG_SINGLE_LAYER_BIND) != 0)
	{
	}

	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_textureType, m_context.getRenderContext()); }
	IterateResult	iterate		(void);

private:
	const TextureFormat		m_format;
	const TextureType		m_textureType;
	const bool				m_singleLayerBind;
};

ImageStoreCase::IterateResult ImageStoreCase::iterate (void)
{
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	TestLog&					log						(m_testCtx.getLog());
	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
	const deUint32				textureTargetGL			= getGLTextureTarget(m_textureType);
	const IVec3&				imageSize				= defaultImageSize(m_textureType);
	const int					numSlicesOrFaces		= m_textureType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
	const int					maxImageDimension		= de::max(imageSize.x(), de::max(imageSize.y(), imageSize.z()));
	const float					storeColorScale			= isFormatTypeUnorm(m_format.type) ? 1.0f / (float)(maxImageDimension - 1)
														: isFormatTypeSnorm(m_format.type) ? 2.0f / (float)(maxImageDimension - 1)
														: 1.0f;
	const float					storeColorBias			= isFormatTypeSnorm(m_format.type) ? -1.0f : 0.0f;
	const glu::Buffer			textureBuf				(renderCtx); // \note Only really used if using buffer texture.
	const glu::Texture			texture					(renderCtx);

	glLog.enableLogging(true);

	// Setup texture.

	log << TestLog::Message << "// Created a texture (name " << *texture << ")" << TestLog::EndMessage;
	if (m_textureType == TEXTURETYPE_BUFFER)
		log << TestLog::Message << "// Created a buffer for the texture (name " << *textureBuf << ")" << TestLog::EndMessage;

	glLog.glActiveTexture(GL_TEXTURE0);
	glLog.glBindTexture(textureTargetGL, *texture);
	setTexParameteri(glLog, textureTargetGL);
	setTextureStorage(glLog, m_textureType, internalFormatGL, imageSize, *textureBuf);

	// Perform image stores in compute shader.

	{
		// Generate compute shader.

		const string		shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
		const TextureType	shaderImageType			= m_singleLayerBind ? textureLayerType(m_textureType) : m_textureType;
		const string		shaderImageTypeStr		= getShaderImageType(m_format.type, shaderImageType);
		const bool			isUintFormat			= isFormatTypeUnsignedInteger(m_format.type);
		const bool			isIntFormat				= isFormatTypeSignedInteger(m_format.type);
		const string		colorBaseExpr			= string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4(gx^gy^gz, "
																												 "(" + toString(imageSize.x()-1) + "-gx)^gy^gz, "
																												 "gx^(" + toString(imageSize.y()-1) + "-gy)^gz, "
																												 "(" + toString(imageSize.x()-1) + "-gx)^(" + toString(imageSize.y()-1) + "-gy)^gz)";
		const string		colorExpr				= colorBaseExpr + (storeColorScale == 1.0f ? "" : "*" + toString(storeColorScale))
																	+ (storeColorBias == 0.0f ? "" : " + float(" + toString(storeColorBias) + ")");
		const std::string	glslVersionDeclaration	= glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));

		const glu::ShaderProgram program(renderCtx,
			glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n"
														+ textureTypeExtensionShaderRequires(shaderImageType, renderCtx) +
														"\n"
														"precision highp " + shaderImageTypeStr + ";\n"
														"\n"
														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
														"layout (" + shaderImageFormatStr + ", binding=0) writeonly uniform " + shaderImageTypeStr + " u_image;\n"
														+ (m_singleLayerBind ? "uniform int u_layerNdx;\n" : "") +
														"\n"
														"void main (void)\n"
														"{\n"
														"	int gx = int(gl_GlobalInvocationID.x);\n"
														"	int gy = int(gl_GlobalInvocationID.y);\n"
														"	int gz = " + (m_singleLayerBind ? "u_layerNdx" : "int(gl_GlobalInvocationID.z)") + ";\n"
														+ (shaderImageType == TEXTURETYPE_BUFFER ?
															"	imageStore(u_image, gx, " + colorExpr + ");\n"
														 : shaderImageType == TEXTURETYPE_2D ?
															"	imageStore(u_image, ivec2(gx, gy), " + colorExpr + ");\n"
														 : shaderImageType == TEXTURETYPE_3D || shaderImageType == TEXTURETYPE_CUBE || shaderImageType == TEXTURETYPE_2D_ARRAY ?
															"	imageStore(u_image, ivec3(gx, gy, gz), " + colorExpr + ");\n"
														 : DE_NULL) +
														"}\n"));

		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());

		log << program;

		if (!program.isOk())
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
			return STOP;
		}

		// Setup and dispatch.

		glLog.glUseProgram(program.getProgram());

		if (m_singleLayerBind)
		{
			for (int layerNdx = 0; layerNdx < numSlicesOrFaces; layerNdx++)
			{
				if (layerNdx > 0)
					glLog.glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

				uniforms.assign1i("u_layerNdx", layerNdx);

				glLog.glBindImageTexture(0, *texture, 0, GL_FALSE, layerNdx, GL_WRITE_ONLY, internalFormatGL);
				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

				glLog.glDispatchCompute(imageSize.x(), imageSize.y(), 1);
				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
			}
		}
		else
		{
			glLog.glBindImageTexture(0, *texture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

			glLog.glDispatchCompute(imageSize.x(), imageSize.y(), numSlicesOrFaces);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
		}
	}

	// Create reference, read texture and compare to reference.
	{
		const int		isIntegerFormat		= isFormatTypeInteger(m_format.type);
		LayeredImage	reference			(m_textureType, m_format, imageSize.x(), imageSize.y(), imageSize.z());

		DE_ASSERT(!isIntegerFormat || (storeColorScale == 1.0f && storeColorBias == 0.0f));

		for (int z = 0; z < numSlicesOrFaces; z++)
		for (int y = 0; y < imageSize.y(); y++)
		for (int x = 0; x < imageSize.x(); x++)
		{
			const IVec4 color(x^y^z, (imageSize.x()-1-x)^y^z, x^(imageSize.y()-1-y)^z, (imageSize.x()-1-x)^(imageSize.y()-1-y)^z);

			if (isIntegerFormat)
				reference.setPixel(x, y, z, color);
			else
				reference.setPixel(x, y, z, color.asFloat()*storeColorScale + storeColorBias);
		}

		const bool compareOk = readTextureAndVerify(renderCtx, glLog, *texture, *textureBuf, m_textureType, m_format, imageSize, ImageLayerComparer(reference));

		m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, compareOk ? "Pass" : "Image comparison failed");
		return STOP;
	}
}

//! Case that copies an image to another, using imageLoad() and imageStore(). Texture formats don't necessarily match image formats.
class ImageLoadAndStoreCase : public TestCase
{
public:
	enum CaseFlag
	{
		CASEFLAG_SINGLE_LAYER_BIND	= 1 << 0,	//!< If given, glBindImageTexture() is called with GL_FALSE <layered> argument, and for each layer the compute shader is separately dispatched.
		CASEFLAG_RESTRICT_IMAGES	= 1 << 1	//!< If given, images in shader will be qualified with "restrict".
	};

	ImageLoadAndStoreCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType textureType, deUint32 caseFlags = 0)
		: TestCase				(context, name, description)
		, m_textureFormat		(format)
		, m_imageFormat			(format)
		, m_textureType			(textureType)
		, m_restrictImages		((caseFlags & CASEFLAG_RESTRICT_IMAGES)		!= 0)
		, m_singleLayerBind		((caseFlags & CASEFLAG_SINGLE_LAYER_BIND)	!= 0)
	{
	}

	ImageLoadAndStoreCase (Context& context, const char* name, const char* description, const TextureFormat& textureFormat, const TextureFormat& imageFormat, TextureType textureType, deUint32 caseFlags = 0)
		: TestCase				(context, name, description)
		, m_textureFormat		(textureFormat)
		, m_imageFormat			(imageFormat)
		, m_textureType			(textureType)
		, m_restrictImages		((caseFlags & CASEFLAG_RESTRICT_IMAGES)		!= 0)
		, m_singleLayerBind		((caseFlags & CASEFLAG_SINGLE_LAYER_BIND)	!= 0)
	{
		DE_ASSERT(textureFormat.getPixelSize() == imageFormat.getPixelSize());
	}

	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_textureType, m_context.getRenderContext()); }
	IterateResult	iterate		(void);

private:
	template <TextureFormat::ChannelType ImageFormatType, typename TcuFloatType, typename TcuFloatStorageType>
	static void					replaceBadFloatReinterpretValues (LayeredImage& image, const TextureFormat& imageFormat);

	const TextureFormat			m_textureFormat;
	const TextureFormat			m_imageFormat;
	const TextureType			m_textureType;
	const bool					m_restrictImages;
	const bool					m_singleLayerBind;
};

template <TextureFormat::ChannelType ImageFormatType, typename TcuFloatType, typename TcuFloatTypeStorageType>
void ImageLoadAndStoreCase::replaceBadFloatReinterpretValues (LayeredImage& image, const TextureFormat& imageFormat)
{
	// Find potential bad values, such as nan or inf, and replace with something else.
	const int		pixelSize			= imageFormat.getPixelSize();
	const int		imageNumChannels	= imageFormat.order == tcu::TextureFormat::R	? 1
										: imageFormat.order == tcu::TextureFormat::RGBA	? 4
										: 0;
	const IVec3		imageSize			= image.getSize();
	const int		numSlicesOrFaces	= image.getImageType() == TEXTURETYPE_CUBE ? 6 : imageSize.z();

	DE_ASSERT(pixelSize % imageNumChannels == 0);

	for (int z = 0; z < numSlicesOrFaces; z++)
	{
		const PixelBufferAccess		sliceAccess		= image.getImageType() == TEXTURETYPE_CUBE ? image.getCubeFaceAccess((tcu::CubeFace)z) : image.getSliceAccess(z);
		const int					rowPitch		= sliceAccess.getRowPitch();
		void *const					data			= sliceAccess.getDataPtr();

		for (int y = 0; y < imageSize.y(); y++)
		for (int x = 0; x < imageSize.x(); x++)
		{
			void *const pixelData = (deUint8*)data + y*rowPitch + x*pixelSize;

			for (int c = 0; c < imageNumChannels; c++)
			{
				void *const			channelData		= (deUint8*)pixelData + c*pixelSize/imageNumChannels;
				const TcuFloatType	f				(*(TcuFloatTypeStorageType*)channelData);

				if (f.isDenorm() || f.isInf() || f.isNaN())
					*(TcuFloatTypeStorageType*)channelData = TcuFloatType(0.0f).bits();
			}
		}
	}
}

ImageLoadAndStoreCase::IterateResult ImageLoadAndStoreCase::iterate (void)
{
	const RenderContext&		renderCtx					= m_context.getRenderContext();
	TestLog&					log							(m_testCtx.getLog());
	glu::CallLogWrapper			glLog						(renderCtx.getFunctions(), log);
	const deUint32				textureInternalFormatGL		= glu::getInternalFormat(m_textureFormat);
	const deUint32				imageInternalFormatGL		= glu::getInternalFormat(m_imageFormat);
	const deUint32				textureTargetGL				= getGLTextureTarget(m_textureType);
	const IVec3&				imageSize					= defaultImageSize(m_textureType);
	const int					maxImageDimension			= de::max(imageSize.x(), de::max(imageSize.y(), imageSize.z()));
	const float					storeColorScale				= isFormatTypeUnorm(m_textureFormat.type) ? 1.0f / (float)(maxImageDimension - 1)
															: isFormatTypeSnorm(m_textureFormat.type) ? 2.0f / (float)(maxImageDimension - 1)
															: 1.0f;
	const float					storeColorBias				= isFormatTypeSnorm(m_textureFormat.type) ? -1.0f : 0.0f;
	const int					numSlicesOrFaces			= m_textureType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
	const bool					isIntegerTextureFormat		= isFormatTypeInteger(m_textureFormat.type);
	const glu::Buffer			texture0Buf					(renderCtx);
	const glu::Buffer			texture1Buf					(renderCtx);
	const glu::Texture			texture0					(renderCtx);
	const glu::Texture			texture1					(renderCtx);
	LayeredImage				reference					(m_textureType, m_textureFormat, imageSize.x(), imageSize.y(), imageSize.z());

	glLog.enableLogging(true);

	// Setup textures.

	log << TestLog::Message << "// Created 2 textures (names " << *texture0 << " and " << *texture1 << ")" << TestLog::EndMessage;
	if (m_textureType == TEXTURETYPE_BUFFER)
		log << TestLog::Message << "// Created buffers for the textures (names " << *texture0Buf << " and " << *texture1Buf << ")" << TestLog::EndMessage;

	// First, fill reference with (a fairly arbitrary) initial pattern. This will be used as texture upload source data as well as for actual reference computation later on.

	DE_ASSERT(!isIntegerTextureFormat || (storeColorScale == 1.0f && storeColorBias == 0.0f));

	for (int z = 0; z < numSlicesOrFaces; z++)
	for (int y = 0; y < imageSize.y(); y++)
	for (int x = 0; x < imageSize.x(); x++)
	{
		const IVec4 color(x^y^z, (imageSize.x()-1-x)^y^z, x^(imageSize.y()-1-y)^z, (imageSize.x()-1-x)^(imageSize.y()-1-y)^z);

		if (isIntegerTextureFormat)
			reference.setPixel(x, y, z, color);
		else
			reference.setPixel(x, y, z, color.asFloat()*storeColorScale + storeColorBias);
	}

	// If re-interpreting the texture contents as floating point values, need to get rid of inf, nan etc.
	if (m_imageFormat.type == TextureFormat::HALF_FLOAT && m_textureFormat.type != TextureFormat::HALF_FLOAT)
		replaceBadFloatReinterpretValues<TextureFormat::HALF_FLOAT, tcu::Float16, deUint16>(reference, m_imageFormat);
	else if (m_imageFormat.type == TextureFormat::FLOAT && m_textureFormat.type != TextureFormat::FLOAT)
		replaceBadFloatReinterpretValues<TextureFormat::FLOAT, tcu::Float32, deUint32>(reference, m_imageFormat);

	// Upload initial pattern to texture 0.

	glLog.glActiveTexture(GL_TEXTURE0);
	glLog.glBindTexture(textureTargetGL, *texture0);
	setTexParameteri(glLog, textureTargetGL);

	log << TestLog::Message << "// Filling texture " << *texture0 << " with xor pattern" << TestLog::EndMessage;

	uploadTexture(glLog, reference, *texture0Buf);

	// Set storage for texture 1.

	glLog.glActiveTexture(GL_TEXTURE1);
	glLog.glBindTexture(textureTargetGL, *texture1);
	setTexParameteri(glLog, textureTargetGL);
	setTextureStorage(glLog, m_textureType, textureInternalFormatGL, imageSize, *texture1Buf);

	// Perform image loads and stores in compute shader and finalize reference computation.

	{
		// Generate compute shader.

		const char* const		maybeRestrict			= m_restrictImages ? "restrict" : "";
		const string			shaderImageFormatStr	= getShaderImageFormatQualifier(m_imageFormat);
		const TextureType		shaderImageType			= m_singleLayerBind ? textureLayerType(m_textureType) : m_textureType;
		const string			shaderImageTypeStr		= getShaderImageType(m_imageFormat.type, shaderImageType);
		const std::string		glslVersionDeclaration	= glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));

		const glu::ShaderProgram program(renderCtx,
			glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n"
														+ textureTypeExtensionShaderRequires(shaderImageType, renderCtx) +
														"\n"
														"precision highp " + shaderImageTypeStr + ";\n"
														"\n"
														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
														"layout (" + shaderImageFormatStr + ", binding=0) " + maybeRestrict + " readonly uniform " + shaderImageTypeStr + " u_image0;\n"
														"layout (" + shaderImageFormatStr + ", binding=1) " + maybeRestrict + " writeonly uniform " + shaderImageTypeStr + " u_image1;\n"
														"\n"
														"void main (void)\n"
														"{\n"
														+ (shaderImageType == TEXTURETYPE_BUFFER ?
															"	int pos = int(gl_GlobalInvocationID.x);\n"
															"	imageStore(u_image1, pos, imageLoad(u_image0, " + toString(imageSize.x()-1) + "-pos));\n"
														 : shaderImageType == TEXTURETYPE_2D ?
															"	ivec2 pos = ivec2(gl_GlobalInvocationID.xy);\n"
															"	imageStore(u_image1, pos, imageLoad(u_image0, ivec2(" + toString(imageSize.x()-1) + "-pos.x, pos.y)));\n"
														 : shaderImageType == TEXTURETYPE_3D || shaderImageType == TEXTURETYPE_CUBE || shaderImageType == TEXTURETYPE_2D_ARRAY ?
															"	ivec3 pos = ivec3(gl_GlobalInvocationID);\n"
															"	imageStore(u_image1, pos, imageLoad(u_image0, ivec3(" + toString(imageSize.x()-1) + "-pos.x, pos.y, pos.z)));\n"
														 : DE_NULL) +
														"}\n"));

		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());

		log << program;

		if (!program.isOk())
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
			return STOP;
		}

		// Setup and dispatch.

		glLog.glUseProgram(program.getProgram());

		if (m_singleLayerBind)
		{
			for (int layerNdx = 0; layerNdx < numSlicesOrFaces; layerNdx++)
			{
				if (layerNdx > 0)
					glLog.glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

				glLog.glBindImageTexture(0, *texture0, 0, GL_FALSE, layerNdx, GL_READ_ONLY, imageInternalFormatGL);
				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

				glLog.glBindImageTexture(1, *texture1, 0, GL_FALSE, layerNdx, GL_WRITE_ONLY, imageInternalFormatGL);
				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

				glLog.glDispatchCompute(imageSize.x(), imageSize.y(), 1);
				GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
			}
		}
		else
		{
			glLog.glBindImageTexture(0, *texture0, 0, GL_TRUE, 0, GL_READ_ONLY, imageInternalFormatGL);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

			glLog.glBindImageTexture(1, *texture1, 0, GL_TRUE, 0, GL_WRITE_ONLY, imageInternalFormatGL);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

			glLog.glDispatchCompute(imageSize.x(), imageSize.y(), numSlicesOrFaces);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
		}

		// Finalize reference.

		if (m_textureFormat != m_imageFormat)
		{
			// Format re-interpretation case. Read data with image format and write back, with the same image format.
			// We do this because the data may change a little during lookups (e.g. unorm8 -> float; not all unorms can be exactly represented as floats).

			const int					pixelSize		= m_imageFormat.getPixelSize();
			tcu::TextureLevel			scratch			(m_imageFormat, 1, 1);
			const PixelBufferAccess		scratchAccess	= scratch.getAccess();

			for (int z = 0; z < numSlicesOrFaces; z++)
			{
				const PixelBufferAccess		sliceAccess		= m_textureType == TEXTURETYPE_CUBE ? reference.getCubeFaceAccess((tcu::CubeFace)z) : reference.getSliceAccess(z);
				const int					rowPitch		= sliceAccess.getRowPitch();
				void *const					data			= sliceAccess.getDataPtr();

				for (int y = 0; y < imageSize.y(); y++)
				for (int x = 0; x < imageSize.x(); x++)
				{
					void *const pixelData = (deUint8*)data + y*rowPitch + x*pixelSize;

					deMemcpy(scratchAccess.getDataPtr(), pixelData, pixelSize);

					if (isFormatTypeInteger(m_imageFormat.type))
						scratchAccess.setPixel(scratchAccess.getPixelUint(0, 0), 0, 0);
					else
						scratchAccess.setPixel(scratchAccess.getPixel(0, 0), 0, 0);

					deMemcpy(pixelData, scratchAccess.getDataPtr(), pixelSize);
				}
			}
		}

		for (int z = 0; z < numSlicesOrFaces; z++)
		for (int y = 0; y < imageSize.y(); y++)
		for (int x = 0; x < imageSize.x()/2; x++)
		{
			if (isIntegerTextureFormat)
			{
				const UVec4 temp = reference.getPixelUint(imageSize.x()-1-x, y, z);
				reference.setPixel(imageSize.x()-1-x, y, z, reference.getPixelUint(x, y, z));
				reference.setPixel(x, y, z, temp);
			}
			else
			{
				const Vec4 temp = reference.getPixel(imageSize.x()-1-x, y, z);
				reference.setPixel(imageSize.x()-1-x, y, z, reference.getPixel(x, y, z));
				reference.setPixel(x, y, z, temp);
			}
		}
	}

	// Read texture 1 and compare to reference.

	const bool compareOk = readTextureAndVerify(renderCtx, glLog, *texture1, *texture1Buf, m_textureType, m_textureFormat, imageSize, ImageLayerComparer(reference));

	m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, compareOk ? "Pass" : "Image comparison failed");
	return STOP;
}

enum AtomicOperationCaseType
{
	ATOMIC_OPERATION_CASE_TYPE_END_RESULT = 0,	//!< Atomic case checks the end result of the operations, and not the return values.
	ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES,	//!< Atomic case checks the return values of the atomic function, and not the end result.

	ATOMIC_OPERATION_CASE_TYPE_LAST
};

/*--------------------------------------------------------------------*//*!
 * \brief Binary atomic operation case.
 *
 * Case that performs binary atomic operations (i.e. any but compSwap) and
 * verifies according to the given AtomicOperationCaseType.
 *
 * For the "end result" case type, a single texture (and image) is created,
 * upon which the atomic operations operate. A compute shader is dispatched
 * with dimensions equal to the image size, except with a bigger X size
 * so that every pixel is operated on by multiple invocations. The end
 * results are verified in BinaryAtomicOperationCase::EndResultVerifier.
 * The return values of the atomic function calls are ignored.
 *
 * For the "return value" case type, the case does much the same operations
 * as in the "end result" case, but also creates an additional texture,
 * of size equal to the dispatch size, into which the return values of the
 * atomic functions are stored (with imageStore()). The return values are
 * verified in BinaryAtomicOperationCase::ReturnValueVerifier.
 * The end result values are not checked.
 *
 * The compute shader invocations contributing to a pixel (X, Y, Z) in the
 * end result image are the invocations with global IDs
 * (X, Y, Z), (X+W, Y, Z), (X+2*W, Y, Z), ..., (X+(N-1)*W, Y, W), where W
 * is the width of the end result image and N is
 * BinaryAtomicOperationCase::NUM_INVOCATIONS_PER_PIXEL.
 *//*--------------------------------------------------------------------*/
class BinaryAtomicOperationCase : public TestCase
{
public:
									BinaryAtomicOperationCase		(Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, AtomicOperation operation, AtomicOperationCaseType caseType)
		: TestCase		(context, name, description)
		, m_format		(format)
		, m_imageType	(imageType)
		, m_operation	(operation)
		, m_caseType	(caseType)
	{
		DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32)	||
				  m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32)		||
				  (m_format == TextureFormat(TextureFormat::R, TextureFormat::FLOAT) && m_operation == ATOMIC_OPERATION_EXCHANGE));

		DE_ASSERT(m_operation != ATOMIC_OPERATION_COMP_SWAP);
	}

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

private:
	class EndResultVerifier;
	class ReturnValueVerifier;

	static int						getOperationInitialValue		(AtomicOperation op); //!< Appropriate value with which to initialize the texture.
	//! Compute the argument given to the atomic function at the given invocation ID, when the entire dispatch has the given width and height.
	static int						getAtomicFuncArgument			(AtomicOperation op, const IVec3& invocationID, const IVec2& dispatchSizeXY);
	//! Generate the shader expression for the argument given to the atomic function. x, y and z are the identifiers for the invocation ID components.
	static string					getAtomicFuncArgumentShaderStr	(AtomicOperation op, const string& x, const string& y, const string& z, const IVec2& dispatchSizeXY);

	static const int				NUM_INVOCATIONS_PER_PIXEL = 5;

	const TextureFormat				m_format;
	const TextureType				m_imageType;
	const AtomicOperation			m_operation;
	const AtomicOperationCaseType	m_caseType;
};

int BinaryAtomicOperationCase::getOperationInitialValue (AtomicOperation op)
{
	switch (op)
	{
		// \note 18 is just an arbitrary small nonzero value.
		case ATOMIC_OPERATION_ADD:			return 18;
		case ATOMIC_OPERATION_MIN:			return (1<<15) - 1;
		case ATOMIC_OPERATION_MAX:			return 18;
		case ATOMIC_OPERATION_AND:			return (1<<15) - 1;
		case ATOMIC_OPERATION_OR:			return 18;
		case ATOMIC_OPERATION_XOR:			return 18;
		case ATOMIC_OPERATION_EXCHANGE:		return 18;
		default:
			DE_ASSERT(false);
			return -1;
	}
}

int BinaryAtomicOperationCase::getAtomicFuncArgument (AtomicOperation op, const IVec3& invocationID, const IVec2& dispatchSizeXY)
{
	const int x		= invocationID.x();
	const int y		= invocationID.y();
	const int z		= invocationID.z();
	const int wid	= dispatchSizeXY.x();
	const int hei	= dispatchSizeXY.y();

	switch (op)
	{
		// \note Fall-throughs.
		case ATOMIC_OPERATION_ADD:
		case ATOMIC_OPERATION_MIN:
		case ATOMIC_OPERATION_MAX:
		case ATOMIC_OPERATION_AND:
		case ATOMIC_OPERATION_OR:
		case ATOMIC_OPERATION_XOR:
			return x*x + y*y + z*z;

		case ATOMIC_OPERATION_EXCHANGE:
			return (z*wid + x)*hei + y;

		default:
			DE_ASSERT(false);
			return -1;
	}
}

string BinaryAtomicOperationCase::getAtomicFuncArgumentShaderStr (AtomicOperation op, const string& x, const string& y, const string& z, const IVec2& dispatchSizeXY)
{
	switch (op)
	{
		// \note Fall-throughs.
		case ATOMIC_OPERATION_ADD:
		case ATOMIC_OPERATION_MIN:
		case ATOMIC_OPERATION_MAX:
		case ATOMIC_OPERATION_AND:
		case ATOMIC_OPERATION_OR:
		case ATOMIC_OPERATION_XOR:
			return "("+ x+"*"+x +" + "+ y+"*"+y +" + "+ z+"*"+z +")";

		case ATOMIC_OPERATION_EXCHANGE:
			return "((" + z + "*" + toString(dispatchSizeXY.x()) + " + " + x + ")*" + toString(dispatchSizeXY.y()) + " + " + y + ")";

		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

class BinaryAtomicOperationCase::EndResultVerifier : public ImageLayerVerifier
{
public:
	EndResultVerifier (AtomicOperation operation, TextureType imageType) : m_operation(operation), m_imageType(imageType) {}

	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
	{
		const bool		isIntegerFormat		= isFormatTypeInteger(resultSlice.getFormat().type);
		const IVec2		dispatchSizeXY		(NUM_INVOCATIONS_PER_PIXEL*resultSlice.getWidth(), resultSlice.getHeight());

		log << TestLog::Image("EndResults" + toString(sliceOrFaceNdx),
							  "Result Values, " + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
																				   : "slice " + toString(sliceOrFaceNdx)),
							  resultSlice);

		for (int y = 0; y < resultSlice.getHeight(); y++)
		for (int x = 0; x < resultSlice.getWidth(); x++)
		{
			union
			{
				int		i;
				float	f;
			} result;

			if (isIntegerFormat)
				result.i = resultSlice.getPixelInt(x, y).x();
			else
				result.f = resultSlice.getPixel(x, y).x();

			// Compute the arguments that were given to the atomic function in the invocations that contribute to this pixel.

			IVec3	invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
			int		atomicArgs[NUM_INVOCATIONS_PER_PIXEL];

			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
			{
				const IVec3 gid(x + i*resultSlice.getWidth(), y, sliceOrFaceNdx);

				invocationGlobalIDs[i]	= gid;
				atomicArgs[i]			= getAtomicFuncArgument(m_operation, gid, dispatchSizeXY);
			}

			if (isOrderIndependentAtomicOperation(m_operation))
			{
				// Just accumulate the atomic args (and the initial value) according to the operation, and compare.

				DE_ASSERT(isIntegerFormat);

				int reference = getOperationInitialValue(m_operation);

				for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
					reference = computeBinaryAtomicOperationResult(m_operation, reference, atomicArgs[i]);

				if (result.i != reference)
				{
					log << TestLog::Message << "// Failure: end result at pixel " << IVec2(x, y) << " of current layer is " << result.i << TestLog::EndMessage
						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
						<< TestLog::Message << "// Note: data expression values for the IDs are " << arrayStr(atomicArgs) << TestLog::EndMessage
						<< TestLog::Message << "// Note: reference value is " << reference << TestLog::EndMessage;
					return false;
				}
			}
			else if (m_operation == ATOMIC_OPERATION_EXCHANGE)
			{
				// Check that the end result equals one of the atomic args.

				bool matchFound = false;

				for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL && !matchFound; i++)
					matchFound = isIntegerFormat ? result.i == atomicArgs[i]
												 : de::abs(result.f - (float)atomicArgs[i]) <= 0.01f;

				if (!matchFound)
				{
					log << TestLog::Message << "// Failure: invalid value at pixel " << IVec2(x, y) << ": got " << (isIntegerFormat ? toString(result.i) : toString(result.f)) << TestLog::EndMessage
											<< TestLog::Message << "// Note: expected one of " << arrayStr(atomicArgs) << TestLog::EndMessage;

					return false;
				}
			}
			else
				DE_ASSERT(false);
		}

		return true;
	}

private:
	const AtomicOperation	m_operation;
	const TextureType		m_imageType;
};

class BinaryAtomicOperationCase::ReturnValueVerifier : public ImageLayerVerifier
{
public:
	//! \note endResultImageLayerSize is (width, height) of the image operated on by the atomic ops, and not the size of the image where the return values are stored.
	ReturnValueVerifier (AtomicOperation operation, TextureType imageType, const IVec2& endResultImageLayerSize) : m_operation(operation), m_imageType(imageType), m_endResultImageLayerSize(endResultImageLayerSize) {}

	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
	{
		const bool		isIntegerFormat		(isFormatTypeInteger(resultSlice.getFormat().type));
		const IVec2		dispatchSizeXY	(resultSlice.getWidth(), resultSlice.getHeight());

		DE_ASSERT(resultSlice.getWidth()	== NUM_INVOCATIONS_PER_PIXEL*m_endResultImageLayerSize.x()	&&
				  resultSlice.getHeight()	== m_endResultImageLayerSize.y()							&&
				  resultSlice.getDepth()	== 1);

		log << TestLog::Image("ReturnValues" + toString(sliceOrFaceNdx),
							  "Per-Invocation Return Values, "
								   + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
																	  : "slice " + toString(sliceOrFaceNdx)),
							  resultSlice);

		for (int y = 0; y < m_endResultImageLayerSize.y(); y++)
		for (int x = 0; x < m_endResultImageLayerSize.x(); x++)
		{
			union IntFloatArr
			{
				int		i[NUM_INVOCATIONS_PER_PIXEL];
				float	f[NUM_INVOCATIONS_PER_PIXEL];
			};

			// Get the atomic function args and return values for all the invocations that contribute to the pixel (x, y) in the current end result slice.

			IntFloatArr		returnValues;
			IntFloatArr		atomicArgs;
			IVec3			invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
			IVec2			pixelCoords[NUM_INVOCATIONS_PER_PIXEL];

			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
			{
				const IVec2 pixCoord	(x + i*m_endResultImageLayerSize.x(), y);
				const IVec3 gid			(pixCoord.x(), pixCoord.y(), sliceOrFaceNdx);

				invocationGlobalIDs[i]	= gid;
				pixelCoords[i]			= pixCoord;

				if (isIntegerFormat)
				{
					returnValues.i[i]	= resultSlice.getPixelInt(gid.x(), y).x();
					atomicArgs.i[i]		= getAtomicFuncArgument(m_operation, gid, dispatchSizeXY);
				}
				else
				{
					returnValues.f[i]	= resultSlice.getPixel(gid.x(), y).x();
					atomicArgs.f[i]		= (float)getAtomicFuncArgument(m_operation, gid, dispatchSizeXY);
				}
			}

			// Verify that the return values form a valid sequence.

			{
				const bool success = isIntegerFormat ? verifyOperationAccumulationIntermediateValues(m_operation,
																									 getOperationInitialValue(m_operation),
																									 atomicArgs.i,
																									 returnValues.i)

													 : verifyOperationAccumulationIntermediateValues(m_operation,
																									 (float)getOperationInitialValue(m_operation),
																									 atomicArgs.f,
																									 returnValues.f);

				if (!success)
				{
					log << TestLog::Message << "// Failure: intermediate return values at pixels " << arrayStr(pixelCoords) << " of current layer are "
											<< (isIntegerFormat ? arrayStr(returnValues.i) : arrayStr(returnValues.f)) << TestLog::EndMessage
						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
						<< TestLog::Message << "// Note: data expression values for the IDs are "
											<< (isIntegerFormat ? arrayStr(atomicArgs.i) : arrayStr(atomicArgs.f))
											<< "; return values are not a valid result for any order of operations" << TestLog::EndMessage;
					return false;
				}
			}
		}

		return true;
	}

private:
	const AtomicOperation	m_operation;
	const TextureType		m_imageType;
	const IVec2				m_endResultImageLayerSize;

	//! Check whether there exists an ordering of args such that { init*A", init*A*B, ..., init*A*B*...*LAST } is the "returnValues" sequence, where { A, B, ..., LAST } is args, and * denotes the operation.
	//	That is, whether "returnValues" is a valid sequence of intermediate return values when "operation" has been accumulated on "args" (and "init") in some arbitrary order.
	template <typename T>
	static bool verifyOperationAccumulationIntermediateValues (AtomicOperation operation, T init, const T (&args)[NUM_INVOCATIONS_PER_PIXEL], const T (&returnValues)[NUM_INVOCATIONS_PER_PIXEL])
	{
		bool argsUsed[NUM_INVOCATIONS_PER_PIXEL] = { false };

		return verifyRecursive(operation, 0, init, argsUsed, args, returnValues);
	}

	static bool compare (int a, int b)		{ return a == b; }
	static bool compare (float a, float b)	{ return de::abs(a - b) <= 0.01f; }

	//! Depth-first search for verifying the return value sequence.
	template <typename T>
	static bool verifyRecursive (AtomicOperation operation, int index, T valueSoFar, bool (&argsUsed)[NUM_INVOCATIONS_PER_PIXEL], const T (&args)[NUM_INVOCATIONS_PER_PIXEL], const T (&returnValues)[NUM_INVOCATIONS_PER_PIXEL])
	{
		if (index < NUM_INVOCATIONS_PER_PIXEL)
		{
			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
			{
				if (!argsUsed[i] && compare(returnValues[i], valueSoFar))
				{
					argsUsed[i] = true;
					if (verifyRecursive(operation, index+1, computeBinaryAtomicOperationResult(operation, valueSoFar, args[i]), argsUsed, args, returnValues))
						return true;
					argsUsed[i] = false;
				}
			}

			return false;
		}
		else
			return true;
	}
};

void BinaryAtomicOperationCase::init (void)
{
	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic") && !glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)))
		throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension");

	checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType, m_context.getRenderContext());
}

BinaryAtomicOperationCase::IterateResult BinaryAtomicOperationCase::iterate (void)
{
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	TestLog&					log						(m_testCtx.getLog());
	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
	const deUint32				textureTargetGL			= getGLTextureTarget(m_imageType);
	const IVec3&				imageSize				= defaultImageSize(m_imageType);
	const int					numSlicesOrFaces		= m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
	const bool					isUintFormat			= isFormatTypeUnsignedInteger(m_format.type);
	const bool					isIntFormat				= isFormatTypeSignedInteger(m_format.type);
	const glu::Buffer			endResultTextureBuf		(renderCtx);
	const glu::Buffer			returnValueTextureBuf	(renderCtx);
	const glu::Texture			endResultTexture		(renderCtx); //!< Texture for the final result; i.e. the texture on which the atomic operations are done. Size imageSize.
	const glu::Texture			returnValueTexture		(renderCtx); //!< Texture into which the return values are stored if m_caseType == CASETYPE_RETURN_VALUES.
																	 //	  Size imageSize*IVec3(N, 1, 1) or, for cube maps, imageSize*IVec3(N, N, 1) where N is NUM_INVOCATIONS_PER_PIXEL.

	glLog.enableLogging(true);

	// Setup textures.

	log << TestLog::Message << "// Created a texture (name " << *endResultTexture << ") to act as the target of atomic operations" << TestLog::EndMessage;
	if (m_imageType == TEXTURETYPE_BUFFER)
		log << TestLog::Message << "// Created a buffer for the texture (name " << *endResultTextureBuf << ")" << TestLog::EndMessage;

	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
	{
		log << TestLog::Message << "// Created a texture (name " << *returnValueTexture << ") to which the intermediate return values of the atomic operation are stored" << TestLog::EndMessage;
		if (m_imageType == TEXTURETYPE_BUFFER)
			log << TestLog::Message << "// Created a buffer for the texture (name " << *returnValueTextureBuf << ")" << TestLog::EndMessage;
	}

	// Fill endResultTexture with initial pattern.

	{
		const LayeredImage imageData(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());

		{
			const IVec4 initial(getOperationInitialValue(m_operation));

			for (int z = 0; z < numSlicesOrFaces; z++)
			for (int y = 0; y < imageSize.y(); y++)
			for (int x = 0; x < imageSize.x(); x++)
				imageData.setPixel(x, y, z, initial);
		}

		// Upload initial pattern to endResultTexture and bind to image.

		glLog.glActiveTexture(GL_TEXTURE0);
		glLog.glBindTexture(textureTargetGL, *endResultTexture);
		setTexParameteri(glLog, textureTargetGL);

		log << TestLog::Message << "// Filling end-result texture with initial pattern (initial value " << getOperationInitialValue(m_operation) << ")" << TestLog::EndMessage;

		uploadTexture(glLog, imageData, *endResultTextureBuf);
	}

	glLog.glBindImageTexture(0, *endResultTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
	{
		// Set storage for returnValueTexture and bind to image.

		glLog.glActiveTexture(GL_TEXTURE1);
		glLog.glBindTexture(textureTargetGL, *returnValueTexture);
		setTexParameteri(glLog, textureTargetGL);

		log << TestLog::Message << "// Setting storage of return-value texture" << TestLog::EndMessage;
		setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize * (m_imageType == TEXTURETYPE_CUBE ? IVec3(NUM_INVOCATIONS_PER_PIXEL, NUM_INVOCATIONS_PER_PIXEL,	1)
																											 : IVec3(NUM_INVOCATIONS_PER_PIXEL, 1,							1)),
						  *returnValueTextureBuf);

		glLog.glBindImageTexture(1, *returnValueTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
	}

	// Perform image stores in compute shader and finalize reference computation.

	{
		// Generate compute shader.

		const string colorVecTypeName		= string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4";
		const string atomicCoord			= m_imageType == TEXTURETYPE_BUFFER		? "gx % " + toString(imageSize.x())
											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx % " + toString(imageSize.x()) + ", gy)"
											: "ivec3(gx % " + toString(imageSize.x()) + ", gy, gz)";
		const string invocationCoord		= m_imageType == TEXTURETYPE_BUFFER		? "gx"
											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx, gy)"
											: "ivec3(gx, gy, gz)";
		const string atomicArgExpr			= (isUintFormat		? "uint"
											 : isIntFormat		? ""
											 : "float")
												+ getAtomicFuncArgumentShaderStr(m_operation, "gx", "gy", "gz", IVec2(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y()));
		const string atomicInvocation		= string() + getAtomicOperationShaderFuncName(m_operation) + "(u_results, " + atomicCoord + ", " + atomicArgExpr + ")";
		const string shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
		const string shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
		const std::string		glslVersionDeclaration	= glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));

		const glu::ShaderProgram program(renderCtx,
			glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n"
														+ imageAtomicExtensionShaderRequires(renderCtx)
														+ textureTypeExtensionShaderRequires(m_imageType, renderCtx) +
														"\n"
														"precision highp " + shaderImageTypeStr + ";\n"
														"\n"
														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
														"layout (" + shaderImageFormatStr + ", binding=0) coherent uniform " + shaderImageTypeStr + " u_results;\n"
														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
															  "layout (" + shaderImageFormatStr + ", binding=1) writeonly uniform " + shaderImageTypeStr + " u_returnValues;\n"
															: "") +
														"\n"
														"void main (void)\n"
														"{\n"
														"	int gx = int(gl_GlobalInvocationID.x);\n"
														"	int gy = int(gl_GlobalInvocationID.y);\n"
														"	int gz = int(gl_GlobalInvocationID.z);\n"
														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
															"	imageStore(u_returnValues, " + invocationCoord + ", " + colorVecTypeName + "(" + atomicInvocation + "));\n"
														 : m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ?
															"	" + atomicInvocation + ";\n"
														 : DE_NULL) +
														"}\n"));

		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());

		log << program;

		if (!program.isOk())
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
			return STOP;
		}

		// Setup and dispatch.

		glLog.glUseProgram(program.getProgram());

		glLog.glDispatchCompute(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y(), numSlicesOrFaces);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
	}

	// Read texture and check.

	{
		const deUint32								textureToCheckGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTexture
																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTexture
																		: (deUint32)-1;
		const deUint32								textureToCheckBufGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTextureBuf
																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTextureBuf
																		: (deUint32)-1;

		const IVec3									textureToCheckSize	= imageSize * IVec3(m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? 1 : NUM_INVOCATIONS_PER_PIXEL, 1, 1);
		const UniquePtr<const ImageLayerVerifier>	verifier			(m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? new EndResultVerifier(m_operation, m_imageType)
																	   : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES		? new ReturnValueVerifier(m_operation, m_imageType, imageSize.swizzle(0, 1))
																	   : (ImageLayerVerifier*)DE_NULL);

		if (readTextureAndVerify(renderCtx, glLog, textureToCheckGL, textureToCheckBufGL, m_imageType, m_format, textureToCheckSize, *verifier))
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");

		return STOP;
	}
}

/*--------------------------------------------------------------------*//*!
 * \brief Atomic compSwap operation case.
 *
 * Similar in principle to BinaryAtomicOperationCase, but separated for
 * convenience, since the atomic function is somewhat different. Like
 * BinaryAtomicOperationCase, this has separate cases for checking end
 * result and return values.
 *//*--------------------------------------------------------------------*/
class AtomicCompSwapCase : public TestCase
{
public:
									AtomicCompSwapCase		(Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, AtomicOperationCaseType caseType)
		: TestCase		(context, name, description)
		, m_format		(format)
		, m_imageType	(imageType)
		, m_caseType	(caseType)
	{
		DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32)	||
				  m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32));
	}

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

private:
	class EndResultVerifier;
	class ReturnValueVerifier;

	static int						getCompareArg			(const IVec3& invocationID, int imageWidth);
	static int						getAssignArg			(const IVec3& invocationID, int imageWidth);
	static string					getCompareArgShaderStr	(const string& x, const string& y, const string& z, int imageWidth);
	static string					getAssignArgShaderStr	(const string& x, const string& y, const string& z, int imageWidth);

	static const int				NUM_INVOCATIONS_PER_PIXEL = 5;

	const TextureFormat				m_format;
	const TextureType				m_imageType;
	const AtomicOperationCaseType	m_caseType;
};

int AtomicCompSwapCase::getCompareArg (const IVec3& invocationID, int imageWidth)
{
	const int x							= invocationID.x();
	const int y							= invocationID.y();
	const int z							= invocationID.z();
	const int wrapX						= x % imageWidth;
	const int curPixelInvocationNdx		= x / imageWidth;

	return wrapX*wrapX + y*y + z*z + curPixelInvocationNdx*42;
}

int AtomicCompSwapCase::getAssignArg (const IVec3& invocationID, int imageWidth)
{
	return getCompareArg(IVec3(invocationID.x() + imageWidth, invocationID.y(), invocationID.z()), imageWidth);
}

string AtomicCompSwapCase::getCompareArgShaderStr (const string& x, const string& y, const string& z, int imageWidth)
{
	const string wrapX					= "(" + x + "%" + toString(imageWidth) + ")";
	const string curPixelInvocationNdx	= "(" + x + "/" + toString(imageWidth) + ")";

	return "(" +wrapX+"*"+wrapX+ " + " +y+"*"+y+ " + " +z+"*"+z+ " + " + curPixelInvocationNdx + "*42)";
}

string AtomicCompSwapCase::getAssignArgShaderStr (const string& x, const string& y, const string& z, int imageWidth)
{
	const string wrapX					= "(" + x + "%" + toString(imageWidth) + ")";
	const string curPixelInvocationNdx	= "(" + x + "/" + toString(imageWidth) + " + 1)";

	return "(" +wrapX+"*"+wrapX+ " + " +y+"*"+y+ " + " +z+"*"+z+ " + " + curPixelInvocationNdx + "*42)";
}

void AtomicCompSwapCase::init (void)
{
	if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic") && !glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)))
		throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension");

	checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType, m_context.getRenderContext());
}

class AtomicCompSwapCase::EndResultVerifier : public ImageLayerVerifier
{
public:
	EndResultVerifier (TextureType imageType, int imageWidth) : m_imageType(imageType), m_imageWidth(imageWidth) {}

	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
	{
		DE_ASSERT(isFormatTypeInteger(resultSlice.getFormat().type));
		DE_ASSERT(resultSlice.getWidth() == m_imageWidth);

		log << TestLog::Image("EndResults" + toString(sliceOrFaceNdx),
							  "Result Values, " + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
																				   : "slice " + toString(sliceOrFaceNdx)),
							  resultSlice);

		for (int y = 0; y < resultSlice.getHeight(); y++)
		for (int x = 0; x < resultSlice.getWidth(); x++)
		{
			// Compute the value-to-assign arguments that were given to the atomic function in the invocations that contribute to this pixel.
			// One of those should be the result.

			const int	result = resultSlice.getPixelInt(x, y).x();
			IVec3		invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
			int			assignArgs[NUM_INVOCATIONS_PER_PIXEL];

			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
			{
				const IVec3 gid(x + i*resultSlice.getWidth(), y, sliceOrFaceNdx);

				invocationGlobalIDs[i]	= gid;
				assignArgs[i]			= getAssignArg(gid, m_imageWidth);
			}

			{
				bool matchFound = false;
				for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL && !matchFound; i++)
					matchFound = result == assignArgs[i];

				if (!matchFound)
				{
					log << TestLog::Message << "// Failure: invalid value at pixel " << IVec2(x, y) << ": got " << result << TestLog::EndMessage
						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
						<< TestLog::Message << "// Note: expected one of " << arrayStr(assignArgs)
											<< " (those are the values given as the 'data' argument in the invocations that contribute to this pixel)"
											<< TestLog::EndMessage;
					return false;
				}
			}
		}

		return true;
	}

private:
	const TextureType	m_imageType;
	const int			m_imageWidth;
};

class AtomicCompSwapCase::ReturnValueVerifier : public ImageLayerVerifier
{
public:
	//! \note endResultImageLayerSize is (width, height) of the image operated on by the atomic ops, and not the size of the image where the return values are stored.
	ReturnValueVerifier (TextureType imageType, int endResultImageWidth) : m_imageType(imageType), m_endResultImageWidth(endResultImageWidth) {}

	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const
	{
		DE_ASSERT(isFormatTypeInteger(resultSlice.getFormat().type));
		DE_ASSERT(resultSlice.getWidth() == NUM_INVOCATIONS_PER_PIXEL*m_endResultImageWidth);

		log << TestLog::Image("ReturnValues" + toString(sliceOrFaceNdx),
							  "Per-Invocation Return Values, "
								   + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx))))
																	  : "slice " + toString(sliceOrFaceNdx)),
							  resultSlice);

		for (int y = 0; y < resultSlice.getHeight(); y++)
		for (int x = 0; x < m_endResultImageWidth; x++)
		{
			// Get the atomic function args and return values for all the invocations that contribute to the pixel (x, y) in the current end result slice.

			int		returnValues[NUM_INVOCATIONS_PER_PIXEL];
			int		compareArgs[NUM_INVOCATIONS_PER_PIXEL];
			IVec3	invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL];
			IVec2	pixelCoords[NUM_INVOCATIONS_PER_PIXEL];

			for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
			{
				const IVec2 pixCoord	(x + i*m_endResultImageWidth, y);
				const IVec3 gid			(pixCoord.x(), pixCoord.y(), sliceOrFaceNdx);

				pixelCoords[i]			= pixCoord;
				invocationGlobalIDs[i]	= gid;
				returnValues[i]			= resultSlice.getPixelInt(gid.x(), y).x();
				compareArgs[i]			= getCompareArg(gid, m_endResultImageWidth);
			}

			// Verify that the return values form a valid sequence.
			// Due to the way the compare and assign arguments to the atomic calls are organized
			// among the different invocations contributing to the same pixel -- i.e. one invocation
			// compares to A and assigns B, another compares to B and assigns C, and so on, where
			// A<B<C etc -- the first value in the return value sequence must be A, and each following
			// value must be either the same as or the smallest value (among A, B, C, ...) bigger than
			// the one just before it. E.g. sequences A A A A A A A A, A B C D E F G H and
			// A A B B B C D E are all valid sequences (if there were 8 invocations contributing
			// to each pixel).

			{
				int failingNdx = -1;

				{
					int currentAtomicValueNdx = 0;
					for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++)
					{
						if (returnValues[i] == compareArgs[currentAtomicValueNdx])
							continue;
						if (i > 0 && returnValues[i] == compareArgs[currentAtomicValueNdx+1])
						{
							currentAtomicValueNdx++;
							continue;
						}
						failingNdx = i;
						break;
					}
				}

				if (failingNdx >= 0)
				{
					log << TestLog::Message << "// Failure: intermediate return values at pixels " << arrayStr(pixelCoords) << " of current layer are "
											<< arrayStr(returnValues) << TestLog::EndMessage
						<< TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage
						<< TestLog::Message << "// Note: 'compare' argument values for the IDs are " << arrayStr(compareArgs) << TestLog::EndMessage
						<< TestLog::Message << "// Note: expected the return value sequence to fulfill the following conditions:\n"
											<< "// - first value is " << compareArgs[0] << "\n"
											<< "// - each value other than the first is either the same as the one just before it, or the smallest value (in the sequence "
											<< arrayStr(compareArgs) << ") bigger than the one just before it" << TestLog::EndMessage;
					if (failingNdx == 0)
						log << TestLog::Message << "// Note: the first return value (" << returnValues[0] << ") isn't " << compareArgs[0] << TestLog::EndMessage;
					else
						log << TestLog::Message << "// Note: the return value at index " << failingNdx << " (value " << returnValues[failingNdx] << ") "
												<< "is neither " << returnValues[failingNdx-1] << " (the one just before it) "
												<< "nor " << compareArgs[arrayIndexOf(compareArgs, returnValues[failingNdx-1])+1] << " (the smallest value bigger than the one just before it)"
												<< TestLog::EndMessage;

					return false;
				}
			}
		}

		return true;
	}

private:
	const TextureType	m_imageType;
	const int			m_endResultImageWidth;
};

AtomicCompSwapCase::IterateResult AtomicCompSwapCase::iterate (void)
{
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	TestLog&					log						(m_testCtx.getLog());
	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
	const deUint32				textureTargetGL			= getGLTextureTarget(m_imageType);
	const IVec3&				imageSize				= defaultImageSize(m_imageType);
	const int					numSlicesOrFaces		= m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
	const bool					isUintFormat			= isFormatTypeUnsignedInteger(m_format.type);
	const bool					isIntFormat				= isFormatTypeSignedInteger(m_format.type);
	const glu::Buffer			endResultTextureBuf		(renderCtx);
	const glu::Buffer			returnValueTextureBuf	(renderCtx);
	const glu::Texture			endResultTexture		(renderCtx); //!< Texture for the final result; i.e. the texture on which the atomic operations are done. Size imageSize.
	const glu::Texture			returnValueTexture		(renderCtx); //!< Texture into which the return values are stored if m_caseType == CASETYPE_RETURN_VALUES.
																	 //	  Size imageSize*IVec3(N, 1, 1) or, for cube maps, imageSize*IVec3(N, N, 1) where N is NUM_INVOCATIONS_PER_PIXEL.

	DE_ASSERT(isUintFormat || isIntFormat);

	glLog.enableLogging(true);

	// Setup textures.

	log << TestLog::Message << "// Created a texture (name " << *endResultTexture << ") to act as the target of atomic operations" << TestLog::EndMessage;
	if (m_imageType == TEXTURETYPE_BUFFER)
		log << TestLog::Message << "// Created a buffer for the texture (name " << *endResultTextureBuf << ")" << TestLog::EndMessage;

	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
	{
		log << TestLog::Message << "// Created a texture (name " << *returnValueTexture << ") to which the intermediate return values of the atomic operation are stored" << TestLog::EndMessage;
		if (m_imageType == TEXTURETYPE_BUFFER)
			log << TestLog::Message << "// Created a buffer for the texture (name " << *returnValueTextureBuf << ")" << TestLog::EndMessage;
	}

	// Fill endResultTexture with initial pattern.

	{
		const LayeredImage imageData(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());

		{
			for (int z = 0; z < numSlicesOrFaces; z++)
			for (int y = 0; y < imageSize.y(); y++)
			for (int x = 0; x < imageSize.x(); x++)
				imageData.setPixel(x, y, z, IVec4(getCompareArg(IVec3(x, y, z), imageSize.x())));
		}

		// Upload initial pattern to endResultTexture and bind to image.

		glLog.glActiveTexture(GL_TEXTURE0);
		glLog.glBindTexture(textureTargetGL, *endResultTexture);
		setTexParameteri(glLog, textureTargetGL);

		log << TestLog::Message << "// Filling end-result texture with initial pattern" << TestLog::EndMessage;

		uploadTexture(glLog, imageData, *endResultTextureBuf);
	}

	glLog.glBindImageTexture(0, *endResultTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

	if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES)
	{
		// Set storage for returnValueTexture and bind to image.

		glLog.glActiveTexture(GL_TEXTURE1);
		glLog.glBindTexture(textureTargetGL, *returnValueTexture);
		setTexParameteri(glLog, textureTargetGL);

		log << TestLog::Message << "// Setting storage of return-value texture" << TestLog::EndMessage;
		setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize * (m_imageType == TEXTURETYPE_CUBE ? IVec3(NUM_INVOCATIONS_PER_PIXEL, NUM_INVOCATIONS_PER_PIXEL,	1)
																											 : IVec3(NUM_INVOCATIONS_PER_PIXEL, 1,							1)),
						  *returnValueTextureBuf);

		glLog.glBindImageTexture(1, *returnValueTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");
	}

	// Perform atomics in compute shader.

	{
		// Generate compute shader.

		const string colorScalarTypeName	= isUintFormat ? "uint" : isIntFormat ? "int" : DE_NULL;
		const string colorVecTypeName		= string(isUintFormat ? "u" : isIntFormat ? "i" : DE_NULL) + "vec4";
		const string atomicCoord			= m_imageType == TEXTURETYPE_BUFFER		? "gx % " + toString(imageSize.x())
											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx % " + toString(imageSize.x()) + ", gy)"
											: "ivec3(gx % " + toString(imageSize.x()) + ", gy, gz)";
		const string invocationCoord		= m_imageType == TEXTURETYPE_BUFFER		? "gx"
											: m_imageType == TEXTURETYPE_2D			? "ivec2(gx, gy)"
											: "ivec3(gx, gy, gz)";
		const string shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
		const string shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
		const string glslVersionDeclaration	= glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));

		const glu::ShaderProgram program(renderCtx,
			glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n"
														+ imageAtomicExtensionShaderRequires(renderCtx)
														+ textureTypeExtensionShaderRequires(m_imageType, renderCtx) +
														"\n"
														"precision highp " + shaderImageTypeStr + ";\n"
														"\n"
														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
														"layout (" + shaderImageFormatStr + ", binding=0) coherent uniform " + shaderImageTypeStr + " u_results;\n"
														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
															  "layout (" + shaderImageFormatStr + ", binding=1) writeonly uniform " + shaderImageTypeStr + " u_returnValues;\n"
															: "") +
														"\n"
														"void main (void)\n"
														"{\n"
														"	int gx = int(gl_GlobalInvocationID.x);\n"
														"	int gy = int(gl_GlobalInvocationID.y);\n"
														"	int gz = int(gl_GlobalInvocationID.z);\n"
														"	" + colorScalarTypeName + " compare = " + colorScalarTypeName + getCompareArgShaderStr("gx", "gy", "gz", imageSize.x()) + ";\n"
														"	" + colorScalarTypeName + " data    = " + colorScalarTypeName + getAssignArgShaderStr("gx", "gy", "gz", imageSize.x()) + ";\n"
														"	" + colorScalarTypeName + " status  = " + colorScalarTypeName + "(-1);\n"
														"	status = imageAtomicCompSwap(u_results, " + atomicCoord + ", compare, data);\n"
														+ (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ?
															"	imageStore(u_returnValues, " + invocationCoord + ", " + colorVecTypeName + "(status));\n" :
															"") +
														"}\n"));

		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());

		log << program;

		if (!program.isOk())
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
			return STOP;
		}

		// Setup and dispatch.

		glLog.glUseProgram(program.getProgram());

		glLog.glDispatchCompute(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y(), numSlicesOrFaces);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
	}

	// Create reference, read texture and compare.

	{
		const deUint32								textureToCheckGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTexture
																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTexture
																		: (deUint32)-1;

		const deUint32								textureToCheckBufGL	= m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? *endResultTextureBuf
																		: m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? *returnValueTextureBuf
																		: (deUint32)-1;

		// The relevant region of the texture being checked (potentially
		// different from actual texture size for cube maps, because cube maps
		// may have unused pixels due to square size restriction).
		const IVec3									relevantRegion		= imageSize * (m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT	? IVec3(1,							1,							1)
																					 :														  IVec3(NUM_INVOCATIONS_PER_PIXEL,	1,							1));

		const UniquePtr<const ImageLayerVerifier>	verifier			(m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? new EndResultVerifier(m_imageType, imageSize.x())
																	   : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES		? new ReturnValueVerifier(m_imageType, imageSize.x())
																	   : (ImageLayerVerifier*)DE_NULL);

		if (readTextureAndVerify(renderCtx, glLog, textureToCheckGL, textureToCheckBufGL, m_imageType, m_format, relevantRegion, *verifier))
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");

		return STOP;
	}
}

//! Case testing the "coherent" or "volatile" qualifier, along with memoryBarrier() and barrier().
class CoherenceCase : public TestCase
{
public:
	enum Qualifier
	{
		QUALIFIER_COHERENT = 0,
		QUALIFIER_VOLATILE,

		QUALIFIER_LAST
	};

	CoherenceCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, Qualifier qualifier)
		: TestCase		(context, name, description)
		, m_format		(format)
		, m_imageType	(imageType)
		, m_qualifier	(qualifier)
	{
		DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_Y) == DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_X) &&
						 DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_Z) == DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_X));

		DE_ASSERT(qualifier == QUALIFIER_COHERENT || qualifier == QUALIFIER_VOLATILE);

		DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32)	||
				  m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32)		||
				  m_format == TextureFormat(TextureFormat::R, TextureFormat::FLOAT));
	}

	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType, m_context.getRenderContext()); }
	IterateResult	iterate		(void);

private:
	static const int			SHADER_READ_OFFSETS_X[4];
	static const int			SHADER_READ_OFFSETS_Y[4];
	static const int			SHADER_READ_OFFSETS_Z[4];
	static const char* const	SHADER_READ_OFFSETS_X_STR;
	static const char* const	SHADER_READ_OFFSETS_Y_STR;
	static const char* const	SHADER_READ_OFFSETS_Z_STR;

	const TextureFormat		m_format;
	const TextureType		m_imageType;
	const Qualifier			m_qualifier;
};

const int			CoherenceCase::SHADER_READ_OFFSETS_X[4]		=		{ 1, 4, 7, 10 };
const int			CoherenceCase::SHADER_READ_OFFSETS_Y[4]		=		{ 2, 5, 8, 11 };
const int			CoherenceCase::SHADER_READ_OFFSETS_Z[4]		=		{ 3, 6, 9, 12 };
const char* const	CoherenceCase::SHADER_READ_OFFSETS_X_STR	= "int[]( 1, 4, 7, 10 )";
const char* const	CoherenceCase::SHADER_READ_OFFSETS_Y_STR	= "int[]( 2, 5, 8, 11 )";
const char* const	CoherenceCase::SHADER_READ_OFFSETS_Z_STR	= "int[]( 3, 6, 9, 12 )";

CoherenceCase::IterateResult CoherenceCase::iterate (void)
{
	const RenderContext&		renderCtx					= m_context.getRenderContext();
	TestLog&					log							(m_testCtx.getLog());
	glu::CallLogWrapper			glLog						(renderCtx.getFunctions(), log);
	const deUint32				internalFormatGL			= glu::getInternalFormat(m_format);
	const deUint32				textureTargetGL				= getGLTextureTarget(m_imageType);
	const IVec3&				imageSize					= defaultImageSize(m_imageType);
	const int					numSlicesOrFaces			= m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z();
	const bool					isUintFormat				= isFormatTypeUnsignedInteger(m_format.type);
	const bool					isIntFormat					= isFormatTypeSignedInteger(m_format.type);
	const char* const			qualifierName				= m_qualifier == QUALIFIER_COHERENT ? "coherent"
															: m_qualifier == QUALIFIER_VOLATILE ? "volatile"
															: DE_NULL;
	const glu::Buffer			textureBuf					(renderCtx);
	const glu::Texture			texture						(renderCtx);
	const IVec3					numGroups					= IVec3(16, de::min(16, imageSize.y()), de::min(2, numSlicesOrFaces));
	const IVec3					workItemSize				= IVec3(imageSize.x(), imageSize.y(), numSlicesOrFaces);
	const IVec3					localSize					= workItemSize / numGroups;
	const IVec3					minReqMaxLocalSize			= IVec3(128, 128, 64);
	const int					minReqMaxLocalInvocations	= 128;

	DE_ASSERT(workItemSize == localSize*numGroups);
	DE_ASSERT(tcu::boolAll(tcu::lessThanEqual(localSize, minReqMaxLocalSize)));
	DE_ASSERT(localSize.x()*localSize.y()*localSize.z() <= minReqMaxLocalInvocations);
	DE_UNREF(minReqMaxLocalSize);
	DE_UNREF(minReqMaxLocalInvocations);

	glLog.enableLogging(true);

	// Setup texture.

	log << TestLog::Message << "// Created a texture (name " << *texture << ")" << TestLog::EndMessage;
	if (m_imageType == TEXTURETYPE_BUFFER)
		log << TestLog::Message << "// Created a buffer for the texture (name " << *textureBuf << ")" << TestLog::EndMessage;

	glLog.glActiveTexture(GL_TEXTURE0);
	glLog.glBindTexture(textureTargetGL, *texture);
	setTexParameteri(glLog, textureTargetGL);
	setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize, *textureBuf);
	glLog.glBindImageTexture(0, *texture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

	// Perform computations in compute shader.

	{
		// Generate compute shader.

		const string		colorVecTypeName		= string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4";
		const char* const	colorScalarTypeName		= isUintFormat ? "uint" : isIntFormat ? "int" : "float";
		const string		invocationCoord			= m_imageType == TEXTURETYPE_BUFFER		? "gx"
													: m_imageType == TEXTURETYPE_2D			? "ivec2(gx, gy)"
													: "ivec3(gx, gy, gz)";
		const string		shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
		const string		shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
		const string		localSizeX				= de::toString(localSize.x());
		const string		localSizeY				= de::toString(localSize.y());
		const string		localSizeZ				= de::toString(localSize.z());
		const std::string	glslVersionDeclaration	= glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));


		const glu::ShaderProgram program(renderCtx,
			glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n"
														+ textureTypeExtensionShaderRequires(m_imageType, renderCtx) +
														"\n"
														"precision highp " + shaderImageTypeStr + ";\n"
														"\n"
														"layout (local_size_x = " + localSizeX
															+ ", local_size_y = " + localSizeY
															+ ", local_size_z = " + localSizeZ
															+ ") in;\n"
														"layout (" + shaderImageFormatStr + ", binding=0) " + qualifierName + " uniform " + shaderImageTypeStr + " u_image;\n"
														"void main (void)\n"
														"{\n"
														"	int gx = int(gl_GlobalInvocationID.x);\n"
														"	int gy = int(gl_GlobalInvocationID.y);\n"
														"	int gz = int(gl_GlobalInvocationID.z);\n"
														"	imageStore(u_image, " + invocationCoord + ", " + colorVecTypeName + "(gx^gy^gz));\n"
														"\n"
														"	memoryBarrier();\n"
														"	barrier();\n"
														"\n"
														"	" + colorScalarTypeName + " sum = " + colorScalarTypeName + "(0);\n"
														"	int groupBaseX = gx/" + localSizeX + "*" + localSizeX + ";\n"
														"	int groupBaseY = gy/" + localSizeY + "*" + localSizeY + ";\n"
														"	int groupBaseZ = gz/" + localSizeZ + "*" + localSizeZ + ";\n"
														"	int xOffsets[] = " + SHADER_READ_OFFSETS_X_STR + ";\n"
														"	int yOffsets[] = " + SHADER_READ_OFFSETS_Y_STR + ";\n"
														"	int zOffsets[] = " + SHADER_READ_OFFSETS_Z_STR + ";\n"
														"	for (int i = 0; i < " + toString(DE_LENGTH_OF_ARRAY(SHADER_READ_OFFSETS_X)) + "; i++)\n"
														"	{\n"
														"		int readX = groupBaseX + (gx + xOffsets[i]) % " + localSizeX + ";\n"
														"		int readY = groupBaseY + (gy + yOffsets[i]) % " + localSizeY + ";\n"
														"		int readZ = groupBaseZ + (gz + zOffsets[i]) % " + localSizeZ + ";\n"
														"		sum += imageLoad(u_image, " + (m_imageType == TEXTURETYPE_BUFFER	? "readX"
																							 : m_imageType == TEXTURETYPE_2D		? "ivec2(readX, readY)"
																							 : "ivec3(readX, readY, readZ)") + ").x;\n"
														"	}\n"
														"\n"
														"	memoryBarrier();\n"
														"	barrier();\n"
														"\n"
														"	imageStore(u_image, " + invocationCoord + ", " + colorVecTypeName + "(sum));\n"
														"}\n"));

		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());

		log << program;

		if (!program.isOk())
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
			return STOP;
		}

		// Setup and dispatch.

		glLog.glUseProgram(program.getProgram());

		glLog.glDispatchCompute(numGroups.x(), numGroups.y(), numGroups.z());
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
	}

	// Create reference, read texture and compare.

	{
		LayeredImage reference(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());

		{
			LayeredImage base(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z());
			for (int z = 0; z < numSlicesOrFaces; z++)
			for (int y = 0; y < imageSize.y(); y++)
			for (int x = 0; x < imageSize.x(); x++)
				base.setPixel(x, y, z, IVec4(x^y^z));

			for (int z = 0; z < numSlicesOrFaces; z++)
			for (int y = 0; y < imageSize.y(); y++)
			for (int x = 0; x < imageSize.x(); x++)
			{
				const int	groupBaseX	= x / localSize.x() * localSize.x();
				const int	groupBaseY	= y / localSize.y() * localSize.y();
				const int	groupBaseZ	= z / localSize.z() * localSize.z();
				int			sum			= 0;
				for (int i = 0; i < DE_LENGTH_OF_ARRAY(SHADER_READ_OFFSETS_X); i++)
					sum += base.getPixelInt(groupBaseX + (x + SHADER_READ_OFFSETS_X[i]) % localSize.x(),
											groupBaseY + (y + SHADER_READ_OFFSETS_Y[i]) % localSize.y(),
											groupBaseZ + (z + SHADER_READ_OFFSETS_Z[i]) % localSize.z()).x();

				reference.setPixel(x, y, z, IVec4(sum));
			}
		}

		if (readTextureAndVerify(renderCtx, glLog, *texture, *textureBuf, m_imageType, m_format, imageSize, ImageLayerComparer(reference)))
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");

		return STOP;
	}
}

class R32UIImageSingleValueVerifier : public ImageLayerVerifier
{
public:
	R32UIImageSingleValueVerifier (const deUint32 value)					: m_min(value),	m_max(value)	{}
	R32UIImageSingleValueVerifier (const deUint32 min, const deUint32 max)	: m_min(min),	m_max(max)		{}

	bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int) const
	{
		DE_ASSERT(resultSlice.getWidth() == 1 && resultSlice.getHeight() == 1 && resultSlice.getDepth() == 1);
		DE_ASSERT(resultSlice.getFormat() == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32));

		log << TestLog::Message << "// Note: expecting to get value " << (m_min == m_max ? toString(m_min) : "in range [" + toString(m_min) + ", " + toString(m_max) + "]") << TestLog::EndMessage;

		const deUint32 resultValue = resultSlice.getPixelUint(0, 0).x();
		if (!de::inRange(resultValue, m_min, m_max))
		{
			log << TestLog::Message << "// Failure: got value " << resultValue << TestLog::EndMessage;
			return false;
		}
		else
		{
			log << TestLog::Message << "// Success: got value " << resultValue << TestLog::EndMessage;
			return true;
		}
	}

private:
	const deUint32 m_min;
	const deUint32 m_max;
};

//! Tests the imageSize() GLSL function. Stores result in a 1x1 R32UI image. The image with which imageSize() is called isn't read or written, and
//  can thus be qualifier readonly, writeonly, or both.
class ImageSizeCase : public TestCase
{
public:
	enum ImageAccess
	{
		IMAGEACCESS_READ_ONLY = 0,
		IMAGEACCESS_WRITE_ONLY,
		IMAGEACCESS_READ_ONLY_WRITE_ONLY,

		IMAGEACCESS_LAST
	};

	ImageSizeCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, const IVec3& size, ImageAccess imageAccess)
		: TestCase			(context, name, description)
		, m_format			(format)
		, m_imageType		(imageType)
		, m_imageSize		(size)
		, m_imageAccess		(imageAccess)
	{
	}

	void			init		(void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType, m_context.getRenderContext()); }
	IterateResult	iterate		(void);

private:
	const TextureFormat		m_format;
	const TextureType		m_imageType;
	const IVec3				m_imageSize;
	const ImageAccess		m_imageAccess;
};

ImageSizeCase::IterateResult ImageSizeCase::iterate (void)
{
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	TestLog&					log						(m_testCtx.getLog());
	glu::CallLogWrapper			glLog					(renderCtx.getFunctions(), log);
	const deUint32				internalFormatGL		= glu::getInternalFormat(m_format);
	const deUint32				textureTargetGL			= getGLTextureTarget(m_imageType);
	const glu::Buffer			mainTextureBuf			(renderCtx);
	const glu::Texture			mainTexture				(renderCtx);
	const glu::Texture			shaderOutResultTexture	(renderCtx);

	glLog.enableLogging(true);

	// Setup textures.

	log << TestLog::Message << "// Created a texture (name " << *mainTexture << ")" << TestLog::EndMessage;
	if (m_imageType == TEXTURETYPE_BUFFER)
		log << TestLog::Message << "// Created a buffer for the texture (name " << *mainTextureBuf << ")" << TestLog::EndMessage;
	log << TestLog::Message << "// Created a texture (name " << *shaderOutResultTexture << ") for storing the shader output" << TestLog::EndMessage;

	glLog.glActiveTexture(GL_TEXTURE0);
	glLog.glBindTexture(textureTargetGL, *mainTexture);
	setTexParameteri(glLog, textureTargetGL);
	setTextureStorage(glLog, m_imageType, internalFormatGL, m_imageSize, *mainTextureBuf);
	glLog.glBindImageTexture(0, *mainTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

	glLog.glActiveTexture(GL_TEXTURE1);
	glLog.glBindTexture(GL_TEXTURE_2D, *shaderOutResultTexture);
	setTexParameteri(glLog, GL_TEXTURE_2D);
	setTextureStorage(glLog, TEXTURETYPE_2D, GL_R32UI, IVec3(1, 1, 1), 0 /* always 2d texture, no buffer needed */);
	glLog.glBindImageTexture(1, *shaderOutResultTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_R32UI);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

	// Read texture size in compute shader.

	{
		// Generate compute shader.

		const char* const	shaderImageAccessStr	= m_imageAccess == IMAGEACCESS_READ_ONLY			? "readonly"
													: m_imageAccess == IMAGEACCESS_WRITE_ONLY			? "writeonly"
													: m_imageAccess == IMAGEACCESS_READ_ONLY_WRITE_ONLY	? "readonly writeonly"
													: DE_NULL;
		const string		shaderImageFormatStr	= getShaderImageFormatQualifier(m_format);
		const string		shaderImageTypeStr		= getShaderImageType(m_format.type, m_imageType);
		const string		glslVersionDeclaration	= glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));

		const glu::ShaderProgram program(renderCtx,
			glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n"
														+ textureTypeExtensionShaderRequires(m_imageType, renderCtx) +
														"\n"
														"precision highp " + shaderImageTypeStr + ";\n"
														"precision highp uimage2D;\n"
														"\n"
														"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n"
														"layout (" + shaderImageFormatStr + ", binding=0) " + shaderImageAccessStr + " uniform " + shaderImageTypeStr + " u_image;\n"
														"layout (r32ui, binding=1) writeonly uniform uimage2D u_result;\n"
														"void main (void)\n"
														"{\n"
														+ (m_imageType == TEXTURETYPE_BUFFER ?
															"	int result = imageSize(u_image);\n"
														 : m_imageType == TEXTURETYPE_2D || m_imageType == TEXTURETYPE_CUBE ?
															"	ivec2 size = imageSize(u_image);\n"
															"	int result = size.y*1000 + size.x;\n"
														 : m_imageType == TEXTURETYPE_3D || m_imageType == TEXTURETYPE_2D_ARRAY ?
															"	ivec3 size = imageSize(u_image);\n"
															"	int result = size.z*1000000 + size.y*1000 + size.x;\n"
														 : DE_NULL) +
														"	imageStore(u_result, ivec2(0, 0), uvec4(result));\n"
														"}\n"));

		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());

		log << program;

		if (!program.isOk())
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
			return STOP;
		}

		// Setup and dispatch.

		glLog.glUseProgram(program.getProgram());

		glLog.glDispatchCompute(1, 1, 1);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute");
	}

	// Read texture and compare to reference.

	{
		const deUint32	referenceOutput		= m_imageType == TEXTURETYPE_BUFFER										? (deUint32)(												  m_imageSize.x())
											: m_imageType == TEXTURETYPE_2D || m_imageType == TEXTURETYPE_CUBE		? (deUint32)(						   m_imageSize.y()*1000 + m_imageSize.x())
											: m_imageType == TEXTURETYPE_3D || m_imageType == TEXTURETYPE_2D_ARRAY	? (deUint32)(m_imageSize.z()*1000000 + m_imageSize.y()*1000 + m_imageSize.x())
											: (deUint32)-1;

		if (readIntegerTextureViaFBOAndVerify(renderCtx, glLog, *shaderOutResultTexture, TEXTURETYPE_2D, TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32),
											  IVec3(1, 1, 1), R32UIImageSingleValueVerifier(referenceOutput)))
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong value");

		return STOP;
	}
}

//! Case testing the control over early/late fragment tests.
class EarlyFragmentTestsCase : public TestCase
{
public:
	enum TestType
	{
		TESTTYPE_DEPTH = 0,
		TESTTYPE_STENCIL,

		TESTTYPE_LAST
	};

	enum RenderTargetType
	{
		RENDERTARGET_DEFAULT = 0,
		RENDERTARGET_FBO,
		RENDERTARGET_FBO_WITHOUT_TEST_ATTACHMENT,

		RENDERTARGET_LAST
	};


	EarlyFragmentTestsCase (Context& context, const char* name, const char* description, TestType type, bool useEarlyTests, RenderTargetType renderTarget)
		: TestCase			(context, name, description)
		, m_type			(type)
		, m_useEarlyTests	(useEarlyTests)
		, m_renderTarget	(renderTarget)
	{
	}

	void init (void)
	{
		if (m_context.getContextInfo().getInt(GL_MAX_FRAGMENT_IMAGE_UNIFORMS) == 0)
			throw tcu::NotSupportedError("GL_MAX_FRAGMENT_IMAGE_UNIFORMS is zero");

		if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic") && !glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)))
			throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension");

		if (m_type == TESTTYPE_DEPTH				&&
			m_renderTarget == RENDERTARGET_DEFAULT	&&
			m_context.getRenderTarget().getDepthBits() == 0)
		{
			throw tcu::NotSupportedError("Test requires depth buffer");
		}

		if (m_type == TESTTYPE_STENCIL				&&
			m_renderTarget == RENDERTARGET_DEFAULT	&&
			m_context.getRenderTarget().getStencilBits() == 0)
		{
			throw tcu::NotSupportedError("Test requires stencil buffer");
		}

		if (m_renderTarget == RENDERTARGET_DEFAULT	&&
			(m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE))
			throw tcu::NotSupportedError("Render target must have at least " + toString(RENDER_SIZE) + " width and height");
	}

	IterateResult iterate (void);

private:
	static const int		RENDER_SIZE;

	const TestType			m_type;
	const bool				m_useEarlyTests;
	const RenderTargetType	m_renderTarget;
};

const int EarlyFragmentTestsCase::RENDER_SIZE = 32;

EarlyFragmentTestsCase::IterateResult EarlyFragmentTestsCase::iterate (void)
{
	const RenderContext&			renderCtx			= m_context.getRenderContext();
	TestLog&						log					(m_testCtx.getLog());
	glu::CallLogWrapper				glLog				(renderCtx.getFunctions(), log);
	de::Random						rnd					(deStringHash(getName()));
	const bool						expectPartialResult	= m_useEarlyTests && m_renderTarget != RENDERTARGET_FBO_WITHOUT_TEST_ATTACHMENT;
	const int						viewportWidth		= RENDER_SIZE;
	const int						viewportHeight		= RENDER_SIZE;
	const int						viewportX			= (m_renderTarget == RENDERTARGET_DEFAULT) ? (rnd.getInt(0, renderCtx.getRenderTarget().getWidth() - viewportWidth))	: (0);
	const int						viewportY			= (m_renderTarget == RENDERTARGET_DEFAULT) ? (rnd.getInt(0, renderCtx.getRenderTarget().getHeight() - viewportHeight))	: (0);
	const glu::Texture				texture				(renderCtx);
	de::MovePtr<glu::Framebuffer>	fbo;
	de::MovePtr<glu::Renderbuffer>	colorAttachment;
	de::MovePtr<glu::Renderbuffer>	testAttachment;

	glLog.enableLogging(true);

	// Setup texture.

	log << TestLog::Message << "// Created a texture (name " << *texture << ")" << TestLog::EndMessage;

	glLog.glActiveTexture(GL_TEXTURE0);
	glLog.glBindTexture(GL_TEXTURE_2D, *texture);
	setTexParameteri(glLog, GL_TEXTURE_2D);
	{
		LayeredImage src(TEXTURETYPE_2D, TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32), 1, 1, 1);
		src.setPixel(0, 0, 0, IVec4(0));
		uploadTexture(glLog, src, 0 /* always 2d texture, no buffer needed */);
	}
	glLog.glBindImageTexture(0, *texture, 0, GL_TRUE, 0, GL_READ_WRITE, GL_R32UI);
	GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture");

	// Set up framebuffer
	if (m_renderTarget == RENDERTARGET_FBO ||
		m_renderTarget == RENDERTARGET_FBO_WITHOUT_TEST_ATTACHMENT)
	{
		fbo				= de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(renderCtx));
		colorAttachment	= de::MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(renderCtx));
		testAttachment	= de::MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(renderCtx));

		glLog.glBindRenderbuffer(GL_RENDERBUFFER, **colorAttachment);
		glLog.glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, RENDER_SIZE, RENDER_SIZE);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "gen color attachment rb");

		glLog.glBindFramebuffer(GL_FRAMEBUFFER, **fbo);
		glLog.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **colorAttachment);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "set fbo color attachment");

		if (m_renderTarget == RENDERTARGET_FBO && m_type == TESTTYPE_DEPTH)
		{
			glLog.glBindRenderbuffer(GL_RENDERBUFFER, **testAttachment);
			glLog.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, RENDER_SIZE, RENDER_SIZE);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "gen depth attachment rb");

			glLog.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, **testAttachment);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "set fbo depth attachment");
		}
		else if (m_renderTarget == RENDERTARGET_FBO && m_type == TESTTYPE_STENCIL)
		{
			glLog.glBindRenderbuffer(GL_RENDERBUFFER, **testAttachment);
			glLog.glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, RENDER_SIZE, RENDER_SIZE);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "gen stencil attachment rb");

			glLog.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, **testAttachment);
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "set fbo stencil attachment");
		}

		glLog.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **colorAttachment);
		GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "setup fbo");
		TCU_CHECK(glLog.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
	}

	// Set up appropriate conditions for the test.

	glLog.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glLog.glClear(GL_COLOR_BUFFER_BIT);

	if (m_type == TESTTYPE_DEPTH)
	{
		glLog.glClearDepthf(0.5f);
		glLog.glClear(GL_DEPTH_BUFFER_BIT);
		glLog.glEnable(GL_DEPTH_TEST);
	}
	else if (m_type == TESTTYPE_STENCIL)
	{
		glLog.glClearStencil(0);
		glLog.glClear(GL_STENCIL_BUFFER_BIT);
		glLog.glScissor(viewportX, viewportY, viewportWidth/2, viewportHeight);
		glLog.glEnable(GL_SCISSOR_TEST);
		glLog.glClearStencil(1);
		glLog.glClear(GL_STENCIL_BUFFER_BIT);
		glLog.glDisable(GL_SCISSOR_TEST);
		glLog.glStencilFunc(GL_EQUAL, 1, 1);
		glLog.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		glLog.glEnable(GL_STENCIL_TEST);
	}
	else
		DE_ASSERT(false);

	// Perform image stores in fragment shader.

	{
		const std::string glslVersionDeclaration = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType()));

		// Generate fragment shader.

		const glu::ShaderProgram program(renderCtx,
			glu::ProgramSources() << glu::VertexSource(		glslVersionDeclaration + "\n"
															"\n"
															"highp in vec3 a_position;\n"
															"\n"
															"void main (void)\n"
															"{\n"
															"	gl_Position = vec4(a_position, 1.0);\n"
															"}\n")

								  << glu::FragmentSource(	glslVersionDeclaration + "\n"
															+ imageAtomicExtensionShaderRequires(renderCtx) +
															"\n"
															+ string(m_useEarlyTests ? "layout (early_fragment_tests) in;\n\n" : "") +
															"layout (location = 0) out highp vec4 o_color;\n"
															"\n"
															"precision highp uimage2D;\n"
															"\n"
															"layout (r32ui, binding=0) coherent uniform uimage2D u_image;\n"
															"\n"
															"void main (void)\n"
															"{\n"
															"	imageAtomicAdd(u_image, ivec2(0, 0), uint(1));\n"
															"	o_color = vec4(1.0);\n"
															"}\n"));

		UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram());

		log << program;

		if (!program.isOk())
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed");
			return STOP;
		}

		// Setup and draw full-viewport quad.

		glLog.glUseProgram(program.getProgram());

		{
			static const float vertexPositions[4*3] =
			{
				-1.0, -1.0, -1.0f,
				 1.0, -1.0,  0.0f,
				-1.0,  1.0,  0.0f,
				 1.0,  1.0,  1.0f,
			};

			static const deUint16 indices[6] = { 0, 1, 2, 2, 1, 3 };

			const glu::VertexArrayBinding attrBindings[] =
			{
				glu::va::Float("a_position", 3, 4, 0, &vertexPositions[0])
			};

			glLog.glViewport(viewportX, viewportY, viewportWidth, viewportHeight);

			glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
				glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
			GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Draw failed");
		}
	}

	// Log rendered result for convenience.
	{
		tcu::Surface rendered(viewportWidth, viewportHeight);
		glu::readPixels(renderCtx, viewportX, viewportY, rendered.getAccess());
		log << TestLog::Image("Rendered", "Rendered image", rendered);
	}

	// Read counter value and check.
	{
		const int numSamples		= de::max(1, renderCtx.getRenderTarget().getNumSamples());
		const int expectedCounter	= expectPartialResult ? viewportWidth*viewportHeight/2				: viewportWidth*viewportHeight;
		const int tolerance			= expectPartialResult ? de::max(viewportWidth, viewportHeight)*3	: 0;
		const int expectedMin		= de::max(0, expectedCounter - tolerance);
		const int expectedMax		= (expectedCounter + tolerance) * numSamples;

		if (readIntegerTextureViaFBOAndVerify(renderCtx, glLog, *texture, TEXTURETYPE_2D, TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32),
											  IVec3(1, 1, 1), R32UIImageSingleValueVerifier(expectedMin, expectedMax)))
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong value");

		return STOP;
	}
}

} // anonymous

ShaderImageLoadStoreTests::ShaderImageLoadStoreTests (Context& context)
	: TestCaseGroup(context, "image_load_store", "Shader Image Load & Store Tests")
{
}

ShaderImageLoadStoreTests::~ShaderImageLoadStoreTests (void)
{
}

void ShaderImageLoadStoreTests::init (void)
{
	// Per-image-type tests.

	{
		static const TextureType imageTypes[] =
		{
			TEXTURETYPE_2D,
			TEXTURETYPE_CUBE,
			TEXTURETYPE_3D,
			TEXTURETYPE_2D_ARRAY,
			TEXTURETYPE_BUFFER
		};

		static const TextureFormat formats[] =
		{
			TextureFormat(TextureFormat::RGBA,	TextureFormat::FLOAT),
			TextureFormat(TextureFormat::RGBA,	TextureFormat::HALF_FLOAT),
			TextureFormat(TextureFormat::R,		TextureFormat::FLOAT),

			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNSIGNED_INT32),
			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNSIGNED_INT16),
			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNSIGNED_INT8),
			TextureFormat(TextureFormat::R,		TextureFormat::UNSIGNED_INT32),

			TextureFormat(TextureFormat::RGBA,	TextureFormat::SIGNED_INT32),
			TextureFormat(TextureFormat::RGBA,	TextureFormat::SIGNED_INT16),
			TextureFormat(TextureFormat::RGBA,	TextureFormat::SIGNED_INT8),
			TextureFormat(TextureFormat::R,		TextureFormat::SIGNED_INT32),

			TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8),

			TextureFormat(TextureFormat::RGBA,	TextureFormat::SNORM_INT8)
		};

		for (int imageTypeNdx = 0; imageTypeNdx < DE_LENGTH_OF_ARRAY(imageTypes); imageTypeNdx++)
		{
			const TextureType		imageType			= imageTypes[imageTypeNdx];
			TestCaseGroup* const	imageTypeGroup		= new TestCaseGroup(m_context, getTextureTypeName(imageType), "");
			addChild(imageTypeGroup);

			TestCaseGroup* const	storeGroup			= new TestCaseGroup(m_context, "store",					"Plain imageStore() cases");
			TestCaseGroup* const	loadStoreGroup		= new TestCaseGroup(m_context, "load_store",			"Cases with imageLoad() followed by imageStore()");
			TestCaseGroup* const	atomicGroup			= new TestCaseGroup(m_context, "atomic",				"Atomic image operation cases");
			TestCaseGroup* const	qualifierGroup		= new TestCaseGroup(m_context, "qualifiers",			"Coherent, volatile and restrict");
			TestCaseGroup* const	reinterpretGroup	= new TestCaseGroup(m_context, "format_reinterpret",	"Cases with differing texture and image formats");
			TestCaseGroup* const	imageSizeGroup		= new TestCaseGroup(m_context, "image_size",			"imageSize() cases");
			imageTypeGroup->addChild(storeGroup);
			imageTypeGroup->addChild(loadStoreGroup);
			imageTypeGroup->addChild(atomicGroup);
			imageTypeGroup->addChild(qualifierGroup);
			imageTypeGroup->addChild(reinterpretGroup);
			imageTypeGroup->addChild(imageSizeGroup);

			for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
			{
				const TextureFormat&	format		= formats[formatNdx];
				const string			formatName	= getShaderImageFormatQualifier(formats[formatNdx]);

				if (imageType == TEXTURETYPE_BUFFER && !isFormatSupportedForTextureBuffer(format))
					continue;

				// Store cases.

				storeGroup->addChild(new ImageStoreCase(m_context, formatName.c_str(), "", format, imageType));
				if (textureLayerType(imageType) != imageType)
					storeGroup->addChild(new ImageStoreCase(m_context, (formatName + "_single_layer").c_str(), "", format, imageType, ImageStoreCase::CASEFLAG_SINGLE_LAYER_BIND));

				// Load & store.

				loadStoreGroup->addChild(new ImageLoadAndStoreCase(m_context, formatName.c_str(), "", format, imageType));
				if (textureLayerType(imageType) != imageType)
					loadStoreGroup->addChild(new ImageLoadAndStoreCase(m_context, (formatName + "_single_layer").c_str(), "", format, imageType, ImageLoadAndStoreCase::CASEFLAG_SINGLE_LAYER_BIND));

				if (format.order == TextureFormat::R)
				{
					// Atomic operations.

					for (int operationI = 0; operationI < ATOMIC_OPERATION_LAST; operationI++)
					{
						for (int atomicCaseTypeI = 0; atomicCaseTypeI < ATOMIC_OPERATION_CASE_TYPE_LAST; atomicCaseTypeI++)
						{
							const AtomicOperation operation = (AtomicOperation)operationI;

							if (format.type == TextureFormat::FLOAT && operation != ATOMIC_OPERATION_EXCHANGE)
								continue;

							const AtomicOperationCaseType	caseType		= (AtomicOperationCaseType)atomicCaseTypeI;
							const string					caseTypeName	= caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT		? "result"
																			: caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES	? "return_value"
																			: DE_NULL;
							const string					caseName		= string() + getAtomicOperationCaseName(operation) + "_" + formatName + "_" + caseTypeName;

							if (operation == ATOMIC_OPERATION_COMP_SWAP)
								atomicGroup->addChild(new AtomicCompSwapCase(m_context, caseName.c_str(), "", format, imageType, caseType));
							else
								atomicGroup->addChild(new BinaryAtomicOperationCase(m_context, caseName.c_str(), "", format, imageType, operation, caseType));
						}
					}

					// Coherence.

					for (int coherenceQualifierI = 0; coherenceQualifierI < CoherenceCase::QUALIFIER_LAST; coherenceQualifierI++)
					{
						const CoherenceCase::Qualifier	coherenceQualifier		= (CoherenceCase::Qualifier)coherenceQualifierI;
						const char* const				coherenceQualifierName	= coherenceQualifier == CoherenceCase::QUALIFIER_COHERENT ? "coherent"
																				: coherenceQualifier == CoherenceCase::QUALIFIER_VOLATILE ? "volatile"
																				: DE_NULL;
						const string					caseName				= string() + coherenceQualifierName + "_" + formatName;

						qualifierGroup->addChild(new CoherenceCase(m_context, caseName.c_str(), "", format, imageType, coherenceQualifier));
					}
				}
			}

			// Restrict.
			qualifierGroup->addChild(new ImageLoadAndStoreCase(m_context, "restrict", "", TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), imageType, ImageLoadAndStoreCase::CASEFLAG_RESTRICT_IMAGES));

			// Format re-interpretation.

			for (int texFmtNdx = 0; texFmtNdx < DE_LENGTH_OF_ARRAY(formats); texFmtNdx++)
			for (int imgFmtNdx = 0; imgFmtNdx < DE_LENGTH_OF_ARRAY(formats); imgFmtNdx++)
			{
				const TextureFormat& texFmt = formats[texFmtNdx];
				const TextureFormat& imgFmt = formats[imgFmtNdx];

				if (imageType == TEXTURETYPE_BUFFER && !isFormatSupportedForTextureBuffer(texFmt))
					continue;

				if (texFmt != imgFmt && texFmt.getPixelSize() == imgFmt.getPixelSize())
					reinterpretGroup->addChild(new ImageLoadAndStoreCase(m_context,
																		 (getShaderImageFormatQualifier(texFmt) + "_" + getShaderImageFormatQualifier(imgFmt)).c_str(), "",
																		 texFmt, imgFmt, imageType));
			}

			// imageSize().

			{
				static const IVec3 baseImageSizes[] =
				{
					IVec3(32, 32, 32),
					IVec3(12, 34, 56),
					IVec3(1,   1,  1),
					IVec3(7,   1,  1)
				};

				for (int imageAccessI = 0; imageAccessI < ImageSizeCase::IMAGEACCESS_LAST; imageAccessI++)
				{
					const ImageSizeCase::ImageAccess	imageAccess		= (ImageSizeCase::ImageAccess)imageAccessI;
					const char* const					imageAccessStr	= imageAccess == ImageSizeCase::IMAGEACCESS_READ_ONLY				? "readonly"
																		: imageAccess == ImageSizeCase::IMAGEACCESS_WRITE_ONLY				? "writeonly"
																		: imageAccess == ImageSizeCase::IMAGEACCESS_READ_ONLY_WRITE_ONLY	? "readonly_writeonly"
																		: DE_NULL;

					for (int imageSizeNdx = 0; imageSizeNdx < DE_LENGTH_OF_ARRAY(baseImageSizes); imageSizeNdx++)
					{
						const IVec3&	baseSize	= baseImageSizes[imageSizeNdx];
						const IVec3		imageSize	= imageType == TEXTURETYPE_BUFFER		? IVec3(baseSize.x(), 1, 1)
													: imageType == TEXTURETYPE_2D			? IVec3(baseSize.x(), baseSize.y(), 1)
													: imageType == TEXTURETYPE_CUBE			? IVec3(baseSize.x(), baseSize.x(), 1)
													: imageType == TEXTURETYPE_3D			? baseSize
													: imageType == TEXTURETYPE_2D_ARRAY		? baseSize
													: IVec3(-1, -1, -1);

						const string	sizeStr		= imageType == TEXTURETYPE_BUFFER		? toString(imageSize.x())
													: imageType == TEXTURETYPE_2D			? toString(imageSize.x()) + "x" + toString(imageSize.y())
													: imageType == TEXTURETYPE_CUBE			? toString(imageSize.x()) + "x" + toString(imageSize.y())
													: imageType == TEXTURETYPE_3D			? toString(imageSize.x()) + "x" + toString(imageSize.y()) + "x" + toString(imageSize.z())
													: imageType == TEXTURETYPE_2D_ARRAY		? toString(imageSize.x()) + "x" + toString(imageSize.y()) + "x" + toString(imageSize.z())
													: DE_NULL;

						const string	caseName	= string() + imageAccessStr + "_" + sizeStr;

						imageSizeGroup->addChild(new ImageSizeCase(m_context, caseName.c_str(), "", TextureFormat(TextureFormat::RGBA, TextureFormat::FLOAT), imageType, imageSize, imageAccess));
					}
				}
			}
		}
	}

	// early_fragment_tests cases.

	{
		TestCaseGroup* const earlyTestsGroup = new TestCaseGroup(m_context, "early_fragment_tests", "");
		addChild(earlyTestsGroup);

		for (int testRenderTargetI = 0; testRenderTargetI < EarlyFragmentTestsCase::RENDERTARGET_LAST; testRenderTargetI++)
		for (int useEarlyTestsI = 0; useEarlyTestsI <= 1; useEarlyTestsI++)
		for (int testTypeI = 0; testTypeI < EarlyFragmentTestsCase::TESTTYPE_LAST; testTypeI++)
		{
			const EarlyFragmentTestsCase::RenderTargetType	targetType		= (EarlyFragmentTestsCase::RenderTargetType)testRenderTargetI;
			const bool										useEarlyTests	= useEarlyTestsI != 0;
			const EarlyFragmentTestsCase::TestType			testType		= (EarlyFragmentTestsCase::TestType)testTypeI;

			const string									testTypeName	= testType == EarlyFragmentTestsCase::TESTTYPE_DEPTH	? "depth"
																			: testType == EarlyFragmentTestsCase::TESTTYPE_STENCIL	? "stencil"
																			: DE_NULL;

			const string									targetName		= targetType == EarlyFragmentTestsCase::RENDERTARGET_FBO							? (std::string("_fbo"))
																			: targetType == EarlyFragmentTestsCase::RENDERTARGET_FBO_WITHOUT_TEST_ATTACHMENT	? (std::string("_fbo_with_no_") + testTypeName)
																			: std::string("");

			const string									caseName		= string(useEarlyTests ? "" : "no_") + "early_fragment_tests_" + testTypeName + targetName;

			const string									caseDesc		= string(useEarlyTests ? "Specify" : "Don't specify")
																			+ " early_fragment_tests, use the " + testTypeName + " test"
																			+ ((targetType == EarlyFragmentTestsCase::RENDERTARGET_FBO)								? (", render to fbo")
																			   : (targetType == EarlyFragmentTestsCase::RENDERTARGET_FBO_WITHOUT_TEST_ATTACHMENT)	? (", render to fbo without relevant buffer")
																			   : (""));

			earlyTestsGroup->addChild(new EarlyFragmentTestsCase(m_context, caseName.c_str(), caseDesc.c_str(), testType, useEarlyTests, targetType));
		}
	}
}

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