/*-------------------------------------------------------------------------
 * 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 Multisample texture test
 *//*--------------------------------------------------------------------*/

#include "es31fTextureMultisampleTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuTextureUtil.hpp"
#include "glsStateQueryUtil.hpp"
#include "glsRasterizationTestUtil.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluObjectWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"

using namespace glw;
using deqp::gls::RasterizationTestUtil::RasterizationArguments;
using deqp::gls::RasterizationTestUtil::TriangleSceneSpec;

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static std::string sampleMaskToString (const std::vector<deUint32>& bitfield, int numBits)
{
	std::string result(numBits, '0');

	// move from back to front and set chars to 1
	for (int wordNdx = 0; wordNdx < (int)bitfield.size(); ++wordNdx)
	{
		for (int bit = 0; bit < 32; ++bit)
		{
			const int targetCharNdx = numBits - (wordNdx*32+bit) - 1;

			// beginning of the string reached
			if (targetCharNdx < 0)
				return result;

			if ((bitfield[wordNdx] >> bit) & 0x01)
				result[targetCharNdx] = '1';
		}
	}

	return result;
}

/*--------------------------------------------------------------------*//*!
 * \brief Returns the number of words needed to represent mask of given length
 *//*--------------------------------------------------------------------*/
static int getEffectiveSampleMaskWordCount (int highestBitNdx)
{
	const int wordSize	= 32;
	const int maskLen	= highestBitNdx + 1;

	return ((maskLen - 1) / wordSize) + 1; // round_up(mask_len /  wordSize)
}

/*--------------------------------------------------------------------*//*!
 * \brief Creates sample mask with all less significant bits than nthBit set
 *//*--------------------------------------------------------------------*/
static std::vector<deUint32> genAllSetToNthBitSampleMask (int nthBit)
{
	const int				wordSize	= 32;
	const int				numWords	= getEffectiveSampleMaskWordCount(nthBit - 1);
	const deUint32			topWordBits	= (deUint32)(nthBit - (numWords - 1) * wordSize);
	std::vector<deUint32>	mask		(numWords);

	for (int ndx = 0; ndx < numWords - 1; ++ndx)
		mask[ndx] = 0xFFFFFFFF;

	mask[numWords - 1] = (deUint32)((1ULL << topWordBits) - (deUint32)1);
	return mask;
}

/*--------------------------------------------------------------------*//*!
 * \brief Creates sample mask with nthBit set
 *//*--------------------------------------------------------------------*/
static std::vector<deUint32> genSetNthBitSampleMask (int nthBit)
{
	const int				wordSize	= 32;
	const int				numWords	= getEffectiveSampleMaskWordCount(nthBit);
	const deUint32			topWordBits	= (deUint32)(nthBit - (numWords - 1) * wordSize);
	std::vector<deUint32>	mask		(numWords);

	for (int ndx = 0; ndx < numWords - 1; ++ndx)
		mask[ndx] = 0;

	mask[numWords - 1] = (deUint32)(1ULL << topWordBits);
	return mask;
}

class SamplePosRasterizationTest : public TestCase
{
public:
								SamplePosRasterizationTest	(Context& context, const char* name, const char* desc, int samples);
								~SamplePosRasterizationTest	(void);

private:
	void						init						(void);
	void						deinit						(void);
	IterateResult				iterate						(void);
	void						genMultisampleTexture		(void);
	void						genSamplerProgram			(void);
	bool						testMultisampleTexture		(int sampleNdx);
	void						drawSample					(tcu::Surface& dst, int sampleNdx);
	void						convertToSceneSpec			(TriangleSceneSpec& scene, const tcu::Vec2& samplePos) const;

	struct Triangle
	{
		tcu::Vec4 p1;
		tcu::Vec4 p2;
		tcu::Vec4 p3;
	};

	const int					m_samples;
	const int					m_canvasSize;
	std::vector<Triangle>		m_testTriangles;

	int							m_iteration;
	bool						m_allIterationsOk;

	GLuint						m_texID;
	GLuint						m_vaoID;
	GLuint						m_vboID;
	std::vector<tcu::Vec2>		m_samplePositions;
	int							m_subpixelBits;

	const glu::ShaderProgram*	m_samplerProgram;
	GLint						m_samplerProgramPosLoc;
	GLint						m_samplerProgramSamplerLoc;
	GLint						m_samplerProgramSampleNdxLoc;
};

SamplePosRasterizationTest::SamplePosRasterizationTest (Context& context, const char* name, const char* desc, int samples)
	: TestCase						(context, name, desc)
	, m_samples						(samples)
	, m_canvasSize					(256)
	, m_iteration					(0)
	, m_allIterationsOk				(true)
	, m_texID						(0)
	, m_vaoID						(0)
	, m_vboID						(0)
	, m_subpixelBits				(0)
	, m_samplerProgram				(DE_NULL)
	, m_samplerProgramPosLoc		(-1)
	, m_samplerProgramSamplerLoc	(-1)
	, m_samplerProgramSampleNdxLoc	(-1)
{
}

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

void SamplePosRasterizationTest::init (void)
{
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	GLint					maxSamples	= 0;

	// requirements

	if (m_context.getRenderTarget().getWidth() < m_canvasSize || m_context.getRenderTarget().getHeight() < m_canvasSize)
		throw tcu::NotSupportedError("render target size must be at least " + de::toString(m_canvasSize) + "x" + de::toString(m_canvasSize));

	gl.getIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxSamples);
	if (m_samples > maxSamples)
		throw tcu::NotSupportedError("Requested sample count is greater than GL_MAX_COLOR_TEXTURE_SAMPLES");

	m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_COLOR_TEXTURE_SAMPLES = " << maxSamples << tcu::TestLog::EndMessage;

	gl.getIntegerv(GL_SUBPIXEL_BITS, &m_subpixelBits);
	m_testCtx.getLog() << tcu::TestLog::Message << "GL_SUBPIXEL_BITS = " << m_subpixelBits << tcu::TestLog::EndMessage;

	// generate textures & other gl stuff

	m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample texture" << tcu::TestLog::EndMessage;

	gl.genTextures				(1, &m_texID);
	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
	gl.texStorage2DMultisample	(GL_TEXTURE_2D_MULTISAMPLE, m_samples, GL_RGBA8, m_canvasSize, m_canvasSize, GL_TRUE);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "texStorage2DMultisample");

	gl.genVertexArrays		(1, &m_vaoID);
	gl.bindVertexArray		(m_vaoID);
	GLU_EXPECT_NO_ERROR		(gl.getError(), "bindVertexArray");

	gl.genBuffers			(1, &m_vboID);
	gl.bindBuffer			(GL_ARRAY_BUFFER, m_vboID);
	GLU_EXPECT_NO_ERROR		(gl.getError(), "bindBuffer");

	// generate test scene
	for (int i = 0; i < 20; ++i)
	{
		// vertical spikes
		Triangle tri;
		tri.p1 = tcu::Vec4(((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	 0.0f,	0.0f,	1.0f);
		tri.p2 = tcu::Vec4(((float)i + 0.3f + 1.0f / (float)(i + 1)) / 20.0f,	 0.0f,	0.0f,	1.0f);
		tri.p3 = tcu::Vec4(((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	-1.0f,	0.0f,	1.0f);
		m_testTriangles.push_back(tri);
	}
	for (int i = 0; i < 20; ++i)
	{
		// horisontal spikes
		Triangle tri;
		tri.p1 = tcu::Vec4(-1.0f,	((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	0.0f,	1.0f);
		tri.p2 = tcu::Vec4(-1.0f,	((float)i + 0.3f + 1.0f / (float)(i + 1)) / 20.0f,	0.0f,	1.0f);
		tri.p3 = tcu::Vec4( 0.0f,	((float)i        + 1.0f / (float)(i + 1)) / 20.0f,	0.0f,	1.0f);
		m_testTriangles.push_back(tri);
	}

	for (int i = 0; i < 20; ++i)
	{
		// fan
		const tcu::Vec2 p = tcu::Vec2(deFloatCos(((float)i)/20.0f*DE_PI*2) * 0.5f + 0.5f, deFloatSin(((float)i)/20.0f*DE_PI*2) * 0.5f + 0.5f);
		const tcu::Vec2 d = tcu::Vec2(0.1f, 0.02f);

		Triangle tri;
		tri.p1 = tcu::Vec4(0.4f,			0.4f,			0.0f,	1.0f);
		tri.p2 = tcu::Vec4(p.x(),			p.y(),			0.0f,	1.0f);
		tri.p3 = tcu::Vec4(p.x() + d.x(),	p.y() + d.y(),	0.0f,	1.0f);
		m_testTriangles.push_back(tri);
	}
	{
		Triangle tri;
		tri.p1 = tcu::Vec4(-0.202f, -0.202f, 0.0f, 1.0f);
		tri.p2 = tcu::Vec4(-0.802f, -0.202f, 0.0f, 1.0f);
		tri.p3 = tcu::Vec4(-0.802f, -0.802f, 0.0f, 1.0f);
		m_testTriangles.push_back(tri);
	}

	// generate multisample texture (and query the sample positions in it)
	genMultisampleTexture();

	// verify queried samples are in a valid range
	for (int sampleNdx = 0; sampleNdx < m_samples; ++sampleNdx)
	{
		if (m_samplePositions[sampleNdx].x() < 0.0f || m_samplePositions[sampleNdx].x() > 1.0f ||
			m_samplePositions[sampleNdx].y() < 0.0f || m_samplePositions[sampleNdx].y() > 1.0f)
		{
			m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Sample position of sample " << sampleNdx << " should be in range ([0, 1], [0, 1]). Got " << m_samplePositions[sampleNdx] << tcu::TestLog::EndMessage;
			throw tcu::TestError("invalid sample position");
		}
	}

	// generate sampler program
	genSamplerProgram();
}

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

	if (m_vaoID)
	{
		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vaoID);
		m_vaoID = 0;
	}

	if (m_texID)
	{
		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texID);
		m_texID = 0;
	}

	if (m_samplerProgram)
	{
		delete m_samplerProgram;
		m_samplerProgram = DE_NULL;
	}
}

SamplePosRasterizationTest::IterateResult SamplePosRasterizationTest::iterate (void)
{
	m_allIterationsOk &= testMultisampleTexture(m_iteration);
	m_iteration++;

	if (m_iteration < m_samples)
		return CONTINUE;

	// End result
	if (m_allIterationsOk)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Pixel comparison failed");

	return STOP;
}

void SamplePosRasterizationTest::genMultisampleTexture (void)
{
	const char* const vertexShaderSource	=	"#version 310 es\n"
												"in highp vec4 a_position;\n"
												"void main (void)\n"
												"{\n"
												"	gl_Position = a_position;\n"
												"}\n";
	const char* const fragmentShaderSource	=	"#version 310 es\n"
												"layout(location = 0) out highp vec4 fragColor;\n"
												"void main (void)\n"
												"{\n"
												"	fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
												"}\n";

	const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
	const glu::ShaderProgram	program			(m_context.getRenderContext(), glu::ProgramSources()
																			<< glu::VertexSource(vertexShaderSource)
																			<< glu::FragmentSource(fragmentShaderSource));
	const GLuint				posLoc			= gl.getAttribLocation(program.getProgram(), "a_position");
	GLuint						fboID			= 0;

	if (!program.isOk())
	{
		m_testCtx.getLog() << program;
		throw tcu::TestError("Failed to build shader.");
	}

	gl.bindTexture			(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
	gl.bindVertexArray		(m_vaoID);
	gl.bindBuffer			(GL_ARRAY_BUFFER, m_vboID);

	// Setup fbo for drawing and for sample position query

	m_testCtx.getLog() << tcu::TestLog::Message << "Attaching texture to FBO" << tcu::TestLog::EndMessage;

	gl.genFramebuffers		(1, &fboID);
	gl.bindFramebuffer		(GL_FRAMEBUFFER, fboID);
	gl.framebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_texID, 0);
	GLU_EXPECT_NO_ERROR		(gl.getError(), "framebufferTexture2D");

	// Query sample positions of the multisample texture by querying the sample positions
	// from an fbo which has the multisample texture as attachment.

	m_testCtx.getLog() << tcu::TestLog::Message << "Sample locations:" << tcu::TestLog::EndMessage;

	for (int sampleNdx = 0; sampleNdx < m_samples; ++sampleNdx)
	{
		gls::StateQueryUtil::StateQueryMemoryWriteGuard<float[2]> position;

		gl.getMultisamplefv(GL_SAMPLE_POSITION, (deUint32)sampleNdx, position);
		if (!position.verifyValidity(m_testCtx))
			throw tcu::TestError("Error while querying sample positions");

		m_testCtx.getLog() << tcu::TestLog::Message << "\t" << sampleNdx << ": (" << position[0] << ", " << position[1] << ")" << tcu::TestLog::EndMessage;
		m_samplePositions.push_back(tcu::Vec2(position[0], position[1]));
	}

	// Draw test pattern to texture

	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test pattern to the texture" << tcu::TestLog::EndMessage;

	gl.bufferData				(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(m_testTriangles.size() * sizeof(Triangle)), &m_testTriangles[0], GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "bufferData");

	gl.viewport					(0, 0, m_canvasSize, m_canvasSize);
	gl.clearColor				(0, 0, 0, 1);
	gl.clear					(GL_COLOR_BUFFER_BIT);
	gl.vertexAttribPointer		(posLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray	(posLoc);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");

	gl.useProgram				(program.getProgram());
	gl.drawArrays				(GL_TRIANGLES, 0, (glw::GLsizei)(m_testTriangles.size() * 3));
	GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");

	gl.disableVertexAttribArray	(posLoc);
	gl.useProgram				(0);
	gl.deleteFramebuffers		(1, &fboID);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "cleanup");
}

void SamplePosRasterizationTest::genSamplerProgram (void)
{
	const char* const	vertexShaderSource	=	"#version 310 es\n"
												"in highp vec4 a_position;\n"
												"void main (void)\n"
												"{\n"
												"	gl_Position = a_position;\n"
												"}\n";
	const char* const	fragShaderSource	=	"#version 310 es\n"
												"layout(location = 0) out highp vec4 fragColor;\n"
												"uniform highp sampler2DMS u_sampler;\n"
												"uniform highp int u_sample;\n"
												"void main (void)\n"
												"{\n"
												"	fragColor = texelFetch(u_sampler, ivec2(int(floor(gl_FragCoord.x)), int(floor(gl_FragCoord.y))), u_sample);\n"
												"}\n";
	const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "Generate sampler shader", "Generate sampler shader");
	const glw::Functions&		gl			=	m_context.getRenderContext().getFunctions();

	m_samplerProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(fragShaderSource));
	m_testCtx.getLog() << *m_samplerProgram;

	if (!m_samplerProgram->isOk())
		throw tcu::TestError("Could not create sampler program.");

	m_samplerProgramPosLoc			= gl.getAttribLocation(m_samplerProgram->getProgram(), "a_position");
	m_samplerProgramSamplerLoc		= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sampler");
	m_samplerProgramSampleNdxLoc	= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sample");
}

bool SamplePosRasterizationTest::testMultisampleTexture (int sampleNdx)
{
	tcu::Surface		glSurface(m_canvasSize, m_canvasSize);
	TriangleSceneSpec	scene;

	// Draw sample
	drawSample(glSurface, sampleNdx);

	// Draw reference(s)
	convertToSceneSpec(scene, m_samplePositions[sampleNdx]);

	// Compare
	{
		RasterizationArguments args;
		args.redBits		= m_context.getRenderTarget().getPixelFormat().redBits;
		args.greenBits		= m_context.getRenderTarget().getPixelFormat().greenBits;
		args.blueBits		= m_context.getRenderTarget().getPixelFormat().blueBits;
		args.numSamples		= 0;
		args.subpixelBits	= m_subpixelBits;

		return gls::RasterizationTestUtil::verifyTriangleGroupRasterization(glSurface, scene, args, m_testCtx.getLog(), deqp::gls::RasterizationTestUtil::VERIFICATIONMODE_STRICT);
	}
}

void SamplePosRasterizationTest::drawSample (tcu::Surface& dst, int sampleNdx)
{
	// Downsample using only one sample
	static const tcu::Vec4 fullscreenQuad[] =
	{
		tcu::Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f)
	};

	const tcu::ScopedLogSection section	(m_testCtx.getLog(), "Test sample position " + de::toString(sampleNdx+1) + "/" + de::toString(m_samples), "Test sample position " + de::toString(sampleNdx+1) + "/" + de::toString(m_samples));
	const glw::Functions&		gl		= m_context.getRenderContext().getFunctions();

	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
	gl.bindVertexArray			(m_vaoID);
	gl.bindBuffer				(GL_ARRAY_BUFFER, m_vboID);

	gl.bufferData				(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), &fullscreenQuad[0], GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "bufferData");

	gl.viewport					(0, 0, m_canvasSize, m_canvasSize);
	gl.clearColor				(0, 0, 0, 1);
	gl.clear					(GL_COLOR_BUFFER_BIT);
	gl.vertexAttribPointer		(m_samplerProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray	(m_samplerProgramPosLoc);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");

	gl.useProgram				(m_samplerProgram->getProgram());
	gl.uniform1i				(m_samplerProgramSamplerLoc, 0);
	gl.uniform1i				(m_samplerProgramSampleNdxLoc, (deInt32)sampleNdx);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "useprogram");

	m_testCtx.getLog() << tcu::TestLog::Message << "Reading from texture with sample index " << sampleNdx << tcu::TestLog::EndMessage;

	gl.drawArrays				(GL_TRIANGLE_STRIP, 0, 4);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");

	gl.disableVertexAttribArray	(m_samplerProgramPosLoc);
	gl.useProgram				(0);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "cleanup");

	gl.finish					();
	glu::readPixels				(m_context.getRenderContext(), 0, 0, dst.getAccess());
	GLU_EXPECT_NO_ERROR			(gl.getError(), "readPixels");
}

void SamplePosRasterizationTest::convertToSceneSpec (TriangleSceneSpec& scene, const tcu::Vec2& samplePos) const
{
	// Triangles are offset from the pixel center by "offset". Move the triangles back to take this into account.
	const tcu::Vec4 offset = tcu::Vec4(samplePos.x() - 0.5f, samplePos.y() - 0.5f, 0.0f, 0.0f) / tcu::Vec4((float)m_canvasSize, (float)m_canvasSize, 1.0f, 1.0f) * 2.0f;

	for (int triangleNdx = 0; triangleNdx < (int)m_testTriangles.size(); ++triangleNdx)
	{
		TriangleSceneSpec::SceneTriangle triangle;

		triangle.positions[0] = m_testTriangles[triangleNdx].p1 - offset;
		triangle.positions[1] = m_testTriangles[triangleNdx].p2 - offset;
		triangle.positions[2] = m_testTriangles[triangleNdx].p3 - offset;

		triangle.sharedEdge[0] = false;
		triangle.sharedEdge[1] = false;
		triangle.sharedEdge[2] = false;

		scene.triangles.push_back(triangle);
	}
}

class SampleMaskCase : public TestCase
{
public:
	enum CaseFlags
	{
		FLAGS_NONE					= 0,
		FLAGS_ALPHA_TO_COVERAGE		= (1ULL << 0),
		FLAGS_SAMPLE_COVERAGE		= (1ULL << 1),
		FLAGS_HIGH_BITS				= (1ULL << 2),
	};

								SampleMaskCase				(Context& context, const char* name, const char* desc, int samples, int flags);
								~SampleMaskCase				(void);

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

	void						genSamplerProgram			(void);
	void						genAlphaProgram				(void);
	void						updateTexture				(int sample);
	bool						verifyTexture				(int sample);
	void						drawSample					(tcu::Surface& dst, int sample);

	const int					m_samples;
	const int					m_canvasSize;
	const int					m_gridsize;
	const int					m_effectiveSampleMaskWordCount;

	int							m_flags;
	int							m_currentSample;
	int							m_allIterationsOk;

	glw::GLuint					m_texID;
	glw::GLuint					m_vaoID;
	glw::GLuint					m_vboID;
	glw::GLuint					m_fboID;

	const glu::ShaderProgram*	m_samplerProgram;
	glw::GLint					m_samplerProgramPosLoc;
	glw::GLint					m_samplerProgramSamplerLoc;
	glw::GLint					m_samplerProgramSampleNdxLoc;

	const glu::ShaderProgram*	m_alphaProgram;
	glw::GLint					m_alphaProgramPosLoc;
};

SampleMaskCase::SampleMaskCase (Context& context, const char* name, const char* desc, int samples, int flags)
	: TestCase						(context, name, desc)
	, m_samples						(samples)
	, m_canvasSize					(256)
	, m_gridsize					(16)
	, m_effectiveSampleMaskWordCount(getEffectiveSampleMaskWordCount(samples - 1))
	, m_flags						(flags)
	, m_currentSample				(-1)
	, m_allIterationsOk				(true)
	, m_texID						(0)
	, m_vaoID						(0)
	, m_vboID						(0)
	, m_fboID						(0)
	, m_samplerProgram				(DE_NULL)
	, m_samplerProgramPosLoc		(-1)
	, m_samplerProgramSamplerLoc	(-1)
	, m_samplerProgramSampleNdxLoc	(-1)
	, m_alphaProgram				(DE_NULL)
	, m_alphaProgramPosLoc			(-1)
{
}

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

void SampleMaskCase::init (void)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	glw::GLint				maxSamples			= 0;
	glw::GLint				maxSampleMaskWords	= 0;

	// requirements

	if (m_context.getRenderTarget().getWidth() < m_canvasSize || m_context.getRenderTarget().getHeight() < m_canvasSize)
		throw tcu::NotSupportedError("render target size must be at least " + de::toString(m_canvasSize) + "x" + de::toString(m_canvasSize));

	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
	if (m_effectiveSampleMaskWordCount > maxSampleMaskWords)
		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

	gl.getIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxSamples);
	if (m_samples > maxSamples)
		throw tcu::NotSupportedError("Requested sample count is greater than GL_MAX_COLOR_TEXTURE_SAMPLES");

	m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_COLOR_TEXTURE_SAMPLES = " << maxSamples << tcu::TestLog::EndMessage;

	// Don't even try to test high bits if there are none

	if ((m_flags & FLAGS_HIGH_BITS) && (m_samples % 32 == 0))
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Sample count is multiple of word size. No unused high bits in sample mask.\nSkipping." << tcu::TestLog::EndMessage;
		throw tcu::NotSupportedError("Test requires unused high bits (sample count not multiple of 32)");
	}

	// generate textures

	m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample texture with sample count " << m_samples << tcu::TestLog::EndMessage;

	gl.genTextures				(1, &m_texID);
	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
	gl.texStorage2DMultisample	(GL_TEXTURE_2D_MULTISAMPLE, m_samples, GL_RGBA8, m_canvasSize, m_canvasSize, GL_FALSE);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "texStorage2DMultisample");

	// attach texture to fbo

	m_testCtx.getLog() << tcu::TestLog::Message << "Attaching texture to FBO" << tcu::TestLog::EndMessage;

	gl.genFramebuffers		(1, &m_fboID);
	gl.bindFramebuffer		(GL_FRAMEBUFFER, m_fboID);
	gl.framebufferTexture2D	(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_texID, 0);
	GLU_EXPECT_NO_ERROR		(gl.getError(), "framebufferTexture2D");

	// buffers

	gl.genVertexArrays		(1, &m_vaoID);
	GLU_EXPECT_NO_ERROR		(gl.getError(), "genVertexArrays");

	gl.genBuffers			(1, &m_vboID);
	gl.bindBuffer			(GL_ARRAY_BUFFER, m_vboID);
	GLU_EXPECT_NO_ERROR		(gl.getError(), "genBuffers");

	// generate grid pattern
	{
		std::vector<tcu::Vec4> gridData(m_gridsize*m_gridsize*6);

		for (int y = 0; y < m_gridsize; ++y)
		for (int x = 0; x < m_gridsize; ++x)
		{
			gridData[(y * m_gridsize + x)*6 + 0] = tcu::Vec4(((float)(x+0) / m_gridsize) * 2.0f - 1.0f, ((float)(y+0) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
			gridData[(y * m_gridsize + x)*6 + 1] = tcu::Vec4(((float)(x+0) / m_gridsize) * 2.0f - 1.0f, ((float)(y+1) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
			gridData[(y * m_gridsize + x)*6 + 2] = tcu::Vec4(((float)(x+1) / m_gridsize) * 2.0f - 1.0f, ((float)(y+1) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
			gridData[(y * m_gridsize + x)*6 + 3] = tcu::Vec4(((float)(x+0) / m_gridsize) * 2.0f - 1.0f, ((float)(y+0) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
			gridData[(y * m_gridsize + x)*6 + 4] = tcu::Vec4(((float)(x+1) / m_gridsize) * 2.0f - 1.0f, ((float)(y+1) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
			gridData[(y * m_gridsize + x)*6 + 5] = tcu::Vec4(((float)(x+1) / m_gridsize) * 2.0f - 1.0f, ((float)(y+0) / m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
		}

		gl.bufferData			(GL_ARRAY_BUFFER, (int)(gridData.size() * sizeof(tcu::Vec4)), gridData[0].getPtr(), GL_STATIC_DRAW);
		GLU_EXPECT_NO_ERROR		(gl.getError(), "bufferData");
	}

	// generate programs

	genSamplerProgram();
	genAlphaProgram();
}

void SampleMaskCase::deinit (void)
{
	if (m_texID)
	{
		m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texID);
		m_texID = 0;
	}
	if (m_vaoID)
	{
		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vaoID);
		m_vaoID = 0;
	}
	if (m_vboID)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vboID);
		m_vboID = 0;
	}
	if (m_fboID)
	{
		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fboID);
		m_fboID = 0;
	}

	if (m_samplerProgram)
	{
		delete m_samplerProgram;
		m_samplerProgram = DE_NULL;
	}
	if (m_alphaProgram)
	{
		delete m_alphaProgram;
		m_alphaProgram = DE_NULL;
	}
}

SampleMaskCase::IterateResult SampleMaskCase::iterate (void)
{
	const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Iteration", (m_currentSample == -1) ? ("Verifying with zero mask") : (std::string() + "Verifying sample " + de::toString(m_currentSample + 1) + "/" + de::toString(m_samples)));

	bool iterationOk;

	// Mask only one sample, clear rest

	updateTexture(m_currentSample);

	// Verify only one sample set is in the texture

	iterationOk = verifyTexture(m_currentSample);
	if (!iterationOk)
		m_allIterationsOk = false;

	m_currentSample++;
	if (m_currentSample < m_samples)
		return CONTINUE;

	// End result

	if (m_allIterationsOk)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else if (m_flags & FLAGS_HIGH_BITS)
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unused mask bits have effect");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Sample test failed");

	return STOP;
}

void SampleMaskCase::genSamplerProgram (void)
{
	const char* const	vertexShaderSource	=	"#version 310 es\n"
												"in highp vec4 a_position;\n"
												"void main (void)\n"
												"{\n"
												"	gl_Position = a_position;\n"
												"}\n";
	const char* const	fragShaderSource	=	"#version 310 es\n"
												"layout(location = 0) out highp vec4 fragColor;\n"
												"uniform highp sampler2DMS u_sampler;\n"
												"uniform highp int u_sample;\n"
												"void main (void)\n"
												"{\n"
												"	highp float correctCoverage = 0.0;\n"
												"	highp float incorrectCoverage = 0.0;\n"
												"	highp ivec2 texelPos = ivec2(int(floor(gl_FragCoord.x)), int(floor(gl_FragCoord.y)));\n"
												"\n"
												"	for (int sampleNdx = 0; sampleNdx < ${NUMSAMPLES}; ++sampleNdx)\n"
												"	{\n"
												"		highp float sampleColor = texelFetch(u_sampler, texelPos, sampleNdx).r;\n"
												"		if (sampleNdx == u_sample)\n"
												"			correctCoverage += sampleColor;\n"
												"		else\n"
												"			incorrectCoverage += sampleColor;\n"
												"	}\n"
												"	fragColor = vec4(correctCoverage, incorrectCoverage, 0.0, 1.0);\n"
												"}\n";
	const tcu::ScopedLogSection			section	(m_testCtx.getLog(), "GenerateSamplerShader", "Generate sampler shader");
	const glw::Functions&				gl		=	m_context.getRenderContext().getFunctions();
	std::map<std::string, std::string>	args;

	args["NUMSAMPLES"] = de::toString(m_samples);

	m_samplerProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(tcu::StringTemplate(fragShaderSource).specialize(args)));
	m_testCtx.getLog() << *m_samplerProgram;

	if (!m_samplerProgram->isOk())
		throw tcu::TestError("Could not create sampler program.");

	m_samplerProgramPosLoc			= gl.getAttribLocation(m_samplerProgram->getProgram(), "a_position");
	m_samplerProgramSamplerLoc		= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sampler");
	m_samplerProgramSampleNdxLoc	= gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sample");
}

void SampleMaskCase::genAlphaProgram (void)
{
	const char* const	vertexShaderSource	=	"#version 310 es\n"
												"in highp vec4 a_position;\n"
												"out highp float v_alpha;\n"
												"void main (void)\n"
												"{\n"
												"	gl_Position = a_position;\n"
												"	v_alpha = (a_position.x * 0.5 + 0.5)*(a_position.y * 0.5 + 0.5);\n"
												"}\n";
	const char* const	fragShaderSource	=	"#version 310 es\n"
												"layout(location = 0) out highp vec4 fragColor;\n"
												"in mediump float v_alpha;\n"
												"void main (void)\n"
												"{\n"
												"	fragColor = vec4(1.0, 1.0, 1.0, v_alpha);\n"
												"}\n";
	const glw::Functions&		gl			=	m_context.getRenderContext().getFunctions();

	m_alphaProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(fragShaderSource));

	if (!m_alphaProgram->isOk())
	{
		m_testCtx.getLog() << *m_alphaProgram;
		throw tcu::TestError("Could not create aplha program.");
	}

	m_alphaProgramPosLoc = gl.getAttribLocation(m_alphaProgram->getProgram(), "a_position");
}

void SampleMaskCase::updateTexture (int sample)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	// prepare draw

	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
	gl.viewport(0, 0, m_canvasSize, m_canvasSize);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);

	// clear all samples

	m_testCtx.getLog() << tcu::TestLog::Message << "Clearing image" << tcu::TestLog::EndMessage;
	gl.clear(GL_COLOR_BUFFER_BIT);

	// set mask state

	if (m_flags & FLAGS_HIGH_BITS)
	{
		const std::vector<deUint32> bitmask			= genSetNthBitSampleMask(sample);
		const std::vector<deUint32>	effectiveMask	= genAllSetToNthBitSampleMask(m_samples);
		std::vector<deUint32>		totalBitmask	(effectiveMask.size());

		DE_ASSERT((int)totalBitmask.size() == m_effectiveSampleMaskWordCount);

		// set some arbitrary high bits to non-effective bits
		for (int wordNdx = 0; wordNdx < (int)effectiveMask.size(); ++wordNdx)
		{
			const deUint32 randomMask	= (deUint32)deUint32Hash(wordNdx << 2 ^ sample);
			const deUint32 sampleMask	= (wordNdx < (int)bitmask.size()) ? (bitmask[wordNdx]) : (0);
			const deUint32 maskMask		= effectiveMask[wordNdx];

			totalBitmask[wordNdx] = (sampleMask & maskMask) | (randomMask & ~maskMask);
		}

		m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask to 0b" << sampleMaskToString(totalBitmask, (int)totalBitmask.size() * 32) << tcu::TestLog::EndMessage;

		gl.enable(GL_SAMPLE_MASK);
		for (int wordNdx = 0; wordNdx < m_effectiveSampleMaskWordCount; ++wordNdx)
		{
			const GLbitfield wordmask = (wordNdx < (int)totalBitmask.size()) ? ((GLbitfield)(totalBitmask[wordNdx])) : (0);
			gl.sampleMaski((deUint32)wordNdx, wordmask);
		}
	}
	else
	{
		const std::vector<deUint32> bitmask = genSetNthBitSampleMask(sample);
		DE_ASSERT((int)bitmask.size() <= m_effectiveSampleMaskWordCount);

		m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask to 0b" << sampleMaskToString(bitmask, m_samples) << tcu::TestLog::EndMessage;

		gl.enable(GL_SAMPLE_MASK);
		for (int wordNdx = 0; wordNdx < m_effectiveSampleMaskWordCount; ++wordNdx)
		{
			const GLbitfield wordmask = (wordNdx < (int)bitmask.size()) ? ((GLbitfield)(bitmask[wordNdx])) : (0);
			gl.sampleMaski((deUint32)wordNdx, wordmask);
		}
	}
	if (m_flags & FLAGS_ALPHA_TO_COVERAGE)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Enabling alpha to coverage." << tcu::TestLog::EndMessage;
		gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE);
	}
	if (m_flags & FLAGS_SAMPLE_COVERAGE)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Enabling sample coverage. Varying sample coverage for grid cells." << tcu::TestLog::EndMessage;
		gl.enable(GL_SAMPLE_COVERAGE);
	}

	// draw test pattern

	m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test grid" << tcu::TestLog::EndMessage;

	gl.bindVertexArray			(m_vaoID);
	gl.bindBuffer				(GL_ARRAY_BUFFER, m_vboID);
	gl.vertexAttribPointer		(m_alphaProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray	(m_alphaProgramPosLoc);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");

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

	for (int y = 0; y < m_gridsize; ++y)
	for (int x = 0; x < m_gridsize; ++x)
	{
		if (m_flags & FLAGS_SAMPLE_COVERAGE)
			gl.sampleCoverage((y*m_gridsize + x) / float(m_gridsize*m_gridsize), GL_FALSE);

		gl.drawArrays				(GL_TRIANGLES, (y*m_gridsize + x) * 6, 6);
		GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");
	}

	// clean state

	gl.disableVertexAttribArray	(m_alphaProgramPosLoc);
	gl.useProgram				(0);
	gl.bindFramebuffer			(GL_FRAMEBUFFER, 0);
	gl.disable					(GL_SAMPLE_MASK);
	gl.disable					(GL_SAMPLE_ALPHA_TO_COVERAGE);
	gl.disable					(GL_SAMPLE_COVERAGE);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "clean");
}

bool SampleMaskCase::verifyTexture (int sample)
{
	tcu::Surface	result		(m_canvasSize, m_canvasSize);
	tcu::Surface	errorMask	(m_canvasSize, m_canvasSize);
	bool			error		= false;

	tcu::clear(errorMask.getAccess(), tcu::RGBA::green.toVec());

	// Draw sample:
	//	Sample sampleNdx is set to red channel
	//	Other samples are set to green channel
	drawSample(result, sample);

	// Check surface contains only sampleNdx
	for (int y = 0; y < m_canvasSize; ++y)
	for (int x = 0; x < m_canvasSize; ++x)
	{
		const tcu::RGBA color					= result.getPixel(x, y);

		// Allow missing coverage with FLAGS_ALPHA_TO_COVERAGE and FLAGS_SAMPLE_COVERAGE, or if there are no samples enabled
		const bool		allowMissingCoverage	= ((m_flags & (FLAGS_ALPHA_TO_COVERAGE | FLAGS_SAMPLE_COVERAGE)) != 0) || (sample == -1);

		// disabled sample was written to
		if (color.getGreen() != 0)
		{
			error = true;
			errorMask.setPixel(x, y, tcu::RGBA::red);
		}
		// enabled sample was not written to
		else if (color.getRed() != 255 && !allowMissingCoverage)
		{
			error = true;
			errorMask.setPixel(x, y, tcu::RGBA::red);
		}
	}

	if (error)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message << "Image verification failed, disabled samples found." << tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("VerificationResult", "Result of rendering")
			<< tcu::TestLog::Image("Result",	"Result",		result)
			<< tcu::TestLog::Image("ErrorMask",	"Error Mask",	errorMask)
			<< tcu::TestLog::EndImageSet;
		return false;
	}
	else
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Image verification ok, no disabled samples found." << tcu::TestLog::EndMessage;
		return true;
	}
}

void SampleMaskCase::drawSample (tcu::Surface& dst, int sample)
{
	// Downsample using only one sample
	static const tcu::Vec4 fullscreenQuad[] =
	{
		tcu::Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f)
	};

	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	glu::Buffer				vertexBuffer	(m_context.getRenderContext());

	gl.bindTexture				(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
	gl.bindVertexArray			(m_vaoID);

	gl.bindBuffer				(GL_ARRAY_BUFFER, *vertexBuffer);
	gl.bufferData				(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), &fullscreenQuad[0], GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "bufferData");

	gl.viewport					(0, 0, m_canvasSize, m_canvasSize);
	gl.clearColor				(0, 0, 0, 1);
	gl.clear					(GL_COLOR_BUFFER_BIT);
	gl.vertexAttribPointer		(m_samplerProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray	(m_samplerProgramPosLoc);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "vertexAttribPointer");

	gl.useProgram				(m_samplerProgram->getProgram());
	gl.uniform1i				(m_samplerProgramSamplerLoc, 0);
	gl.uniform1i				(m_samplerProgramSampleNdxLoc, (deInt32)sample);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "useprogram");

	m_testCtx.getLog() << tcu::TestLog::Message << "Reading from texture with sampler shader, u_sample = " << sample << tcu::TestLog::EndMessage;

	gl.drawArrays				(GL_TRIANGLE_STRIP, 0, 4);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "drawArrays");

	gl.disableVertexAttribArray	(m_samplerProgramPosLoc);
	gl.useProgram				(0);
	GLU_EXPECT_NO_ERROR			(gl.getError(), "cleanup");

	gl.finish					();
	glu::readPixels				(m_context.getRenderContext(), 0, 0, dst.getAccess());
	GLU_EXPECT_NO_ERROR			(gl.getError(), "readPixels");
}

class MultisampleTextureUsageCase : public TestCase
{
public:

	enum TextureType
	{
		TEXTURE_COLOR_2D = 0,
		TEXTURE_COLOR_2D_ARRAY,
		TEXTURE_INT_2D,
		TEXTURE_INT_2D_ARRAY,
		TEXTURE_UINT_2D,
		TEXTURE_UINT_2D_ARRAY,
		TEXTURE_DEPTH_2D,
		TEXTURE_DEPTH_2D_ARRAY,

		TEXTURE_LAST
	};

						MultisampleTextureUsageCase		(Context& ctx, const char* name, const char* desc, int samples, TextureType type);
						~MultisampleTextureUsageCase	(void);

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

	void				genDrawShader					(void);
	void				genSamplerShader				(void);

	void				renderToTexture					(float value);
	void				sampleTexture					(tcu::Surface& dst, float value);
	bool				verifyImage						(const tcu::Surface& dst);

	static const int	s_textureSize					= 256;
	static const int	s_textureArraySize				= 8;
	static const int	s_textureLayer					= 3;

	const TextureType	m_type;
	const int			m_numSamples;

	glw::GLuint			m_fboID;
	glw::GLuint			m_textureID;

	glu::ShaderProgram*	m_drawShader;
	glu::ShaderProgram*	m_samplerShader;

	const bool			m_isColorFormat;
	const bool			m_isSignedFormat;
	const bool			m_isUnsignedFormat;
	const bool			m_isDepthFormat;
	const bool			m_isArrayType;
};

MultisampleTextureUsageCase::MultisampleTextureUsageCase (Context& ctx, const char* name, const char* desc, int samples, TextureType type)
	: TestCase			(ctx, name, desc)
	, m_type			(type)
	, m_numSamples		(samples)
	, m_fboID			(0)
	, m_textureID		(0)
	, m_drawShader		(DE_NULL)
	, m_samplerShader	(DE_NULL)
	, m_isColorFormat	(m_type == TEXTURE_COLOR_2D	|| m_type == TEXTURE_COLOR_2D_ARRAY)
	, m_isSignedFormat	(m_type == TEXTURE_INT_2D	|| m_type == TEXTURE_INT_2D_ARRAY)
	, m_isUnsignedFormat(m_type == TEXTURE_UINT_2D	|| m_type == TEXTURE_UINT_2D_ARRAY)
	, m_isDepthFormat	(m_type == TEXTURE_DEPTH_2D	|| m_type == TEXTURE_DEPTH_2D_ARRAY)
	, m_isArrayType		(m_type == TEXTURE_COLOR_2D_ARRAY || m_type == TEXTURE_INT_2D_ARRAY || m_type == TEXTURE_UINT_2D_ARRAY || m_type == TEXTURE_DEPTH_2D_ARRAY)
{
	DE_ASSERT(m_type < TEXTURE_LAST);
}

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

void MultisampleTextureUsageCase::init (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	const glw::GLenum		internalFormat	= (m_isColorFormat) ? (GL_R8) : (m_isSignedFormat) ? (GL_R8I) : (m_isUnsignedFormat) ? (GL_R8UI) : (m_isDepthFormat) ? (GL_DEPTH_COMPONENT32F) : (0);
	const glw::GLenum		textureTarget	= (m_isArrayType) ? (GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : (GL_TEXTURE_2D_MULTISAMPLE);
	const glw::GLenum		fboAttachment	= (m_isDepthFormat) ? (GL_DEPTH_ATTACHMENT) : (GL_COLOR_ATTACHMENT0);

	DE_ASSERT(internalFormat);

	// requirements

	if (m_isArrayType && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
		throw tcu::NotSupportedError("Test requires OES_texture_storage_multisample_2d_array extension");
	if (m_context.getRenderTarget().getWidth() < s_textureSize || m_context.getRenderTarget().getHeight() < s_textureSize)
		throw tcu::NotSupportedError("render target size must be at least " + de::toString(static_cast<int>(s_textureSize)) + "x" + de::toString(static_cast<int>(s_textureSize)));

	{
		glw::GLint maxSamples = 0;
		gl.getInternalformativ(textureTarget, internalFormat, GL_SAMPLES, 1, &maxSamples);

		if (m_numSamples > maxSamples)
			throw tcu::NotSupportedError("Requested sample count is greater than supported");

		m_testCtx.getLog() << tcu::TestLog::Message << "Max sample count for " << glu::getPixelFormatStr(internalFormat) << ": " << maxSamples << tcu::TestLog::EndMessage;
	}

	{
		GLint maxTextureSize = 0;
		gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);

		if (s_textureSize > maxTextureSize)
			throw tcu::NotSupportedError("Larger GL_MAX_TEXTURE_SIZE is required");
	}

	if (m_isArrayType)
	{
		GLint maxTextureLayers = 0;
		gl.getIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureLayers);

		if (s_textureArraySize > maxTextureLayers)
			throw tcu::NotSupportedError("Larger GL_MAX_ARRAY_TEXTURE_LAYERS is required");
	}

	// create texture

	m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample " << ((m_isDepthFormat) ? ("depth") : ("")) << " texture" << ((m_isArrayType) ? (" array") : ("")) << tcu::TestLog::EndMessage;

	gl.genTextures(1, &m_textureID);
	gl.bindTexture(textureTarget, m_textureID);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bind texture");

	if (m_isArrayType)
		gl.texStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, m_numSamples, internalFormat, s_textureSize, s_textureSize, s_textureArraySize, GL_FALSE);
	else
		gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples, internalFormat, s_textureSize, s_textureSize, GL_FALSE);
	GLU_EXPECT_NO_ERROR(gl.getError(), "texstorage");

	// create fbo for drawing

	gl.genFramebuffers(1, &m_fboID);
	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);

	if (m_isArrayType)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Attaching multisample texture array layer " << static_cast<int>(s_textureLayer) << " to fbo" << tcu::TestLog::EndMessage;
		gl.framebufferTextureLayer(GL_FRAMEBUFFER, fboAttachment, m_textureID, 0, s_textureLayer);
	}
	else
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Attaching multisample texture to fbo" << tcu::TestLog::EndMessage;
		gl.framebufferTexture2D(GL_FRAMEBUFFER, fboAttachment, textureTarget, m_textureID, 0);
	}
	GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

	// create shader for rendering to fbo
	genDrawShader();

	// create shader for sampling the texture rendered to
	genSamplerShader();
}

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

	if (m_fboID)
	{
		m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fboID);
		m_fboID = 0;
	}

	if (m_drawShader)
	{
		delete m_drawShader;
		m_drawShader = DE_NULL;
	}

	if (m_samplerShader)
	{
		delete m_samplerShader;
		m_samplerShader = DE_NULL;
	}
}

MultisampleTextureUsageCase::IterateResult MultisampleTextureUsageCase::iterate (void)
{
	const tcu::ScopedLogSection	section			(m_testCtx.getLog(), "Sample", "Render to texture and sample texture");
	tcu::Surface				result			(s_textureSize, s_textureSize);
	const float					minValue		= (m_isColorFormat || m_isDepthFormat) ? (0.0f) : (m_isSignedFormat) ? (-100.0f) : (m_isUnsignedFormat) ? (0.0f)	: ( 1.0f);
	const float					maxValue		= (m_isColorFormat || m_isDepthFormat) ? (1.0f) : (m_isSignedFormat) ? ( 100.0f) : (m_isUnsignedFormat) ? (200.0f)	: (-1.0f);
	de::Random					rnd				(deUint32Hash((deUint32)m_type));
	const float					rawValue		= rnd.getFloat(minValue, maxValue);
	const float					preparedValue	= (m_isSignedFormat || m_isUnsignedFormat) ? (deFloatFloor(rawValue)) : (rawValue);

	// draw to fbo with a random value

	renderToTexture(preparedValue);

	// draw from texture to front buffer

	sampleTexture(result, preparedValue);

	// result is ok?

	if (verifyImage(result))
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");

	return STOP;
}

void MultisampleTextureUsageCase::genDrawShader (void)
{
	const tcu::ScopedLogSection section(m_testCtx.getLog(), "RenderShader", "Generate render-to-texture shader");

	static const char* const	vertexShaderSource =		"#version 310 es\n"
															"in highp vec4 a_position;\n"
															"void main (void)\n"
															"{\n"
															"	gl_Position = a_position;\n"
															"}\n";
	static const char* const	fragmentShaderSourceColor =	"#version 310 es\n"
															"layout(location = 0) out highp ${OUTTYPE} fragColor;\n"
															"uniform highp float u_writeValue;\n"
															"void main (void)\n"
															"{\n"
															"	fragColor = ${OUTTYPE}(vec4(u_writeValue, 1.0, 1.0, 1.0));\n"
															"}\n";
	static const char* const	fragmentShaderSourceDepth =	"#version 310 es\n"
															"layout(location = 0) out highp vec4 fragColor;\n"
															"uniform highp float u_writeValue;\n"
															"void main (void)\n"
															"{\n"
															"	fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
															"	gl_FragDepth = u_writeValue;\n"
															"}\n";
	const char* const			fragmentSource =			(m_isDepthFormat) ? (fragmentShaderSourceDepth) : (fragmentShaderSourceColor);

	std::map<std::string, std::string> fragmentArguments;

	if (m_isColorFormat || m_isDepthFormat)
		fragmentArguments["OUTTYPE"] = "vec4";
	else if (m_isSignedFormat)
		fragmentArguments["OUTTYPE"] = "ivec4";
	else if (m_isUnsignedFormat)
		fragmentArguments["OUTTYPE"] = "uvec4";
	else
		DE_ASSERT(DE_FALSE);

	m_drawShader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(tcu::StringTemplate(fragmentSource).specialize(fragmentArguments)));
	m_testCtx.getLog() << *m_drawShader;

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

void MultisampleTextureUsageCase::genSamplerShader (void)
{
	const tcu::ScopedLogSection section(m_testCtx.getLog(), "SamplerShader", "Generate texture sampler shader");

	static const char* const vertexShaderSource =	"#version 310 es\n"
													"in highp vec4 a_position;\n"
													"out highp float v_gradient;\n"
													"void main (void)\n"
													"{\n"
													"	gl_Position = a_position;\n"
													"	v_gradient = a_position.x * 0.5 + 0.5;\n"
													"}\n";
	static const char* const fragmentShaderSource =	"#version 310 es\n"
													"${EXTENSION_STATEMENT}"
													"layout(location = 0) out highp vec4 fragColor;\n"
													"uniform highp ${SAMPLERTYPE} u_sampler;\n"
													"uniform highp int u_maxSamples;\n"
													"uniform highp int u_layer;\n"
													"uniform highp float u_cmpValue;\n"
													"in highp float v_gradient;\n"
													"void main (void)\n"
													"{\n"
													"	const highp vec4 okColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
													"	const highp vec4 failColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
													"	const highp float epsilon = ${EPSILON};\n"
													"\n"
													"	highp int sampleNdx = clamp(int(floor(v_gradient * float(u_maxSamples))), 0, u_maxSamples-1);\n"
													"	highp float value = float(texelFetch(u_sampler, ${FETCHPOS}, sampleNdx).r);\n"
													"	fragColor = (abs(u_cmpValue - value) < epsilon) ? (okColor) : (failColor);\n"
													"}\n";

	std::map<std::string, std::string> fragmentArguments;

	if (m_isArrayType)
		fragmentArguments["FETCHPOS"] = "ivec3(floor(gl_FragCoord.xy), u_layer)";
	else
		fragmentArguments["FETCHPOS"] = "ivec2(floor(gl_FragCoord.xy))";

	if (m_isColorFormat || m_isDepthFormat)
		fragmentArguments["EPSILON"] = "0.1";
	else
		fragmentArguments["EPSILON"] = "1.0";

	if (m_isArrayType)
		fragmentArguments["EXTENSION_STATEMENT"] = "#extension GL_OES_texture_storage_multisample_2d_array : require\n";
	else
		fragmentArguments["EXTENSION_STATEMENT"] = "";

	switch (m_type)
	{
		case TEXTURE_COLOR_2D:			fragmentArguments["SAMPLERTYPE"] = "sampler2DMS";		break;
		case TEXTURE_COLOR_2D_ARRAY:	fragmentArguments["SAMPLERTYPE"] = "sampler2DMSArray";	break;
		case TEXTURE_INT_2D:			fragmentArguments["SAMPLERTYPE"] = "isampler2DMS";		break;
		case TEXTURE_INT_2D_ARRAY:		fragmentArguments["SAMPLERTYPE"] = "isampler2DMSArray";	break;
		case TEXTURE_UINT_2D:			fragmentArguments["SAMPLERTYPE"] = "usampler2DMS";		break;
		case TEXTURE_UINT_2D_ARRAY:		fragmentArguments["SAMPLERTYPE"] = "usampler2DMSArray";	break;
		case TEXTURE_DEPTH_2D:			fragmentArguments["SAMPLERTYPE"] = "sampler2DMS";		break;
		case TEXTURE_DEPTH_2D_ARRAY:	fragmentArguments["SAMPLERTYPE"] = "sampler2DMSArray";	break;

		default:
			DE_ASSERT(DE_FALSE);
	}

	m_samplerShader = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(vertexShaderSource) << glu::FragmentSource(tcu::StringTemplate(fragmentShaderSource).specialize(fragmentArguments)));
	m_testCtx.getLog() << *m_samplerShader;

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

void MultisampleTextureUsageCase::renderToTexture (float value)
{
	static const tcu::Vec4 fullscreenQuad[] =
	{
		tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
		tcu::Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f,  1.0f, 0.0f, 1.0f),
	};

	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const int				posLocation			= gl.getAttribLocation(m_drawShader->getProgram(), "a_position");
	const int				valueLocation		= gl.getUniformLocation(m_drawShader->getProgram(), "u_writeValue");
	glu::Buffer				vertexAttibBuffer	(m_context.getRenderContext());

	m_testCtx.getLog() << tcu::TestLog::Message << "Filling multisample texture with value " << value  << tcu::TestLog::EndMessage;

	// upload data

	gl.bindBuffer(GL_ARRAY_BUFFER, *vertexAttibBuffer);
	gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad[0].getPtr(), GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bufferdata");

	// clear buffer

	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
	gl.viewport(0, 0, s_textureSize, s_textureSize);

	if (m_isColorFormat)
	{
		const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
		gl.clearBufferfv(GL_COLOR, 0, clearColor);
	}
	else if (m_isSignedFormat)
	{
		const deInt32 clearColor[4] = { 0, 0, 0, 0 };
		gl.clearBufferiv(GL_COLOR, 0, clearColor);
	}
	else if (m_isUnsignedFormat)
	{
		const deUint32 clearColor[4] = { 0, 0, 0, 0 };
		gl.clearBufferuiv(GL_COLOR, 0, clearColor);
	}
	else if (m_isDepthFormat)
	{
		const float clearDepth = 0.5f;
		gl.clearBufferfv(GL_DEPTH, 0, &clearDepth);
	}

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

	// setup shader and draw

	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray(posLocation);

	gl.useProgram(m_drawShader->getProgram());
	gl.uniform1f(valueLocation, value);

	GLU_EXPECT_NO_ERROR(gl.getError(), "setup draw");

	if (m_isDepthFormat)
	{
		gl.enable(GL_DEPTH_TEST);
		gl.depthFunc(GL_ALWAYS);
	}

	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

	// clean state

	if (m_isDepthFormat)
		gl.disable(GL_DEPTH_TEST);

	gl.disableVertexAttribArray(posLocation);
	gl.useProgram(0);
	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clean");
}

void MultisampleTextureUsageCase::sampleTexture (tcu::Surface& dst, float value)
{
	static const tcu::Vec4 fullscreenQuad[] =
	{
		tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
		tcu::Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f),
		tcu::Vec4( 1.0f,  1.0f, 0.0f, 1.0f),
	};

	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const int				posLocation			= gl.getAttribLocation(m_samplerShader->getProgram(), "a_position");
	const int				samplerLocation		= gl.getUniformLocation(m_samplerShader->getProgram(), "u_sampler");
	const int				maxSamplesLocation	= gl.getUniformLocation(m_samplerShader->getProgram(), "u_maxSamples");
	const int				layerLocation		= gl.getUniformLocation(m_samplerShader->getProgram(), "u_layer");
	const int				valueLocation		= gl.getUniformLocation(m_samplerShader->getProgram(), "u_cmpValue");
	const glw::GLenum		textureTarget		= (m_isArrayType) ? (GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : (GL_TEXTURE_2D_MULTISAMPLE);
	glu::Buffer				vertexAttibBuffer	(m_context.getRenderContext());

	m_testCtx.getLog() << tcu::TestLog::Message << "Sampling from texture." << tcu::TestLog::EndMessage;

	// upload data

	gl.bindBuffer(GL_ARRAY_BUFFER, *vertexAttibBuffer);
	gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad[0].getPtr(), GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bufferdata");

	// clear

	gl.viewport(0, 0, s_textureSize, s_textureSize);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	// setup shader and draw

	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.enableVertexAttribArray(posLocation);

	gl.useProgram(m_samplerShader->getProgram());
	gl.uniform1i(samplerLocation, 0);
	gl.uniform1i(maxSamplesLocation, m_numSamples);
	if (m_isArrayType)
		gl.uniform1i(layerLocation, s_textureLayer);
	gl.uniform1f(valueLocation, value);
	gl.bindTexture(textureTarget, m_textureID);
	GLU_EXPECT_NO_ERROR(gl.getError(), "setup draw");

	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

	// clean state

	gl.disableVertexAttribArray(posLocation);
	gl.useProgram(0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clean");

	// read results
	gl.finish();
	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

bool MultisampleTextureUsageCase::verifyImage (const tcu::Surface& dst)
{
	bool error = false;

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

	for (int y = 0; y < dst.getHeight(); ++y)
	for (int x = 0; x < dst.getWidth(); ++x)
	{
		const tcu::RGBA color				= dst.getPixel(x, y);
		const int		colorThresholdRed	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().redBits);
		const int		colorThresholdGreen	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().greenBits);
		const int		colorThresholdBlue	= 1 << (8 - m_context.getRenderTarget().getPixelFormat().blueBits);

		// only green is accepted
		if (color.getRed() > colorThresholdRed || color.getGreen() < 255 - colorThresholdGreen || color.getBlue() > colorThresholdBlue)
			error = true;
	}

	if (error)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Invalid pixels found." << tcu::TestLog::EndMessage;
		m_testCtx.getLog()
			<< tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result", dst)
			<< tcu::TestLog::EndImageSet;

		return false;
	}
	else
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
		return true;
	}
}

class NegativeFramebufferCase : public TestCase
{
public:
	enum CaseType
	{
		CASE_DIFFERENT_N_SAMPLES_TEX = 0,
		CASE_DIFFERENT_N_SAMPLES_RBO,
		CASE_DIFFERENT_FIXED_TEX,
		CASE_DIFFERENT_FIXED_RBO,
		CASE_NON_ZERO_LEVEL,

		CASE_LAST
	};

						NegativeFramebufferCase		(Context& context, const char* name, const char* desc, CaseType caseType);
						~NegativeFramebufferCase	(void);

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

	void				getFormatSamples			(glw::GLenum target, std::vector<int>& samples);

	const CaseType		m_caseType;
	const int			m_fboSize;
	const glw::GLenum	m_internalFormat;

	int					m_numSamples0;	// !< samples for attachment 0
	int					m_numSamples1;	// !< samples for attachment 1
};

NegativeFramebufferCase::NegativeFramebufferCase (Context& context, const char* name, const char* desc, CaseType caseType)
	: TestCase			(context, name, desc)
	, m_caseType		(caseType)
	, m_fboSize			(64)
	, m_internalFormat	(GL_RGBA8)
	, m_numSamples0		(-1)
	, m_numSamples1		(-1)
{
}

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

void NegativeFramebufferCase::init (void)
{
	const bool				colorAttachmentTexture	= (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_TEX);
	const bool				colorAttachmentRbo		= (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) || (m_caseType == CASE_DIFFERENT_FIXED_RBO);
	const bool				useDifferentSampleCounts= (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO);
	std::vector<int>		textureSamples;
	std::vector<int>		rboSamples;

	getFormatSamples(GL_TEXTURE_2D_MULTISAMPLE, textureSamples);
	getFormatSamples(GL_RENDERBUFFER, rboSamples);

	TCU_CHECK(!textureSamples.empty());
	TCU_CHECK(!rboSamples.empty());

	// select sample counts

	if (useDifferentSampleCounts)
	{
		if (colorAttachmentTexture)
		{
			m_numSamples0 = textureSamples[0];

			if (textureSamples.size() >= 2)
				m_numSamples1 = textureSamples[1];
			else
				throw tcu::NotSupportedError("Test requires multiple supported sample counts");
		}
		else if (colorAttachmentRbo)
		{
			for (int texNdx = 0; texNdx < (int)textureSamples.size(); ++texNdx)
			for (int rboNdx = 0; rboNdx < (int)rboSamples.size(); ++rboNdx)
			{
				if (textureSamples[texNdx] != rboSamples[rboNdx])
				{
					m_numSamples0 = textureSamples[texNdx];
					m_numSamples1 = rboSamples[rboNdx];
					return;
				}
			}

			throw tcu::NotSupportedError("Test requires multiple supported sample counts");
		}
		else
			DE_ASSERT(DE_FALSE);
	}
	else
	{
		if (colorAttachmentTexture)
		{
			m_numSamples0 = textureSamples[0];
			m_numSamples1 = textureSamples[0];
		}
		else if (colorAttachmentRbo)
		{
			for (int texNdx = 0; texNdx < (int)textureSamples.size(); ++texNdx)
			for (int rboNdx = 0; rboNdx < (int)rboSamples.size(); ++rboNdx)
			{
				if (textureSamples[texNdx] == rboSamples[rboNdx])
				{
					m_numSamples0 = textureSamples[texNdx];
					m_numSamples1 = rboSamples[rboNdx];
					return;
				}
			}

			throw tcu::NotSupportedError("Test requires a sample count supported in both rbo and texture");
		}
		else
		{
			m_numSamples0 = textureSamples[0];
		}
	}
}

void NegativeFramebufferCase::deinit (void)
{
}

NegativeFramebufferCase::IterateResult NegativeFramebufferCase::iterate (void)
{
	const bool				colorAttachmentTexture	= (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_TEX);
	const bool				colorAttachmentRbo		= (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) || (m_caseType == CASE_DIFFERENT_FIXED_RBO);
	const glw::GLenum		fixedSampleLocations0	= (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) ? (GL_TRUE) : (GL_FALSE);
	const glw::GLenum		fixedSampleLocations1	= ((m_caseType == CASE_DIFFERENT_FIXED_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_RBO)) ? (GL_TRUE) : (GL_FALSE);
	glu::CallLogWrapper		gl						(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	glw::GLuint				fboId					= 0;
	glw::GLuint				rboId					= 0;
	glw::GLuint				tex0Id					= 0;
	glw::GLuint				tex1Id					= 0;

	bool					testFailed				= false;

	gl.enableLogging(true);

	try
	{
		gl.glGenFramebuffers(1, &fboId);
		gl.glBindFramebuffer(GL_FRAMEBUFFER, fboId);
		GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen fbo");

		gl.glGenTextures(1, &tex0Id);
		gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex0Id);
		gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples0, m_internalFormat, m_fboSize, m_fboSize, fixedSampleLocations0);
		GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen texture 0");

		if (m_caseType == CASE_NON_ZERO_LEVEL)
		{
			glw::GLenum error;

			// attaching non-zero level generates invalid value
			gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex0Id, 5);
			error = gl.glGetError();

			if (error != GL_INVALID_VALUE)
			{
				m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_VALUE, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
				testFailed = true;
			}
		}
		else
		{
			gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex0Id, 0);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c0");

			if (colorAttachmentTexture)
			{
				gl.glGenTextures(1, &tex1Id);
				gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex1Id);
				gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples1, m_internalFormat, m_fboSize, m_fboSize, fixedSampleLocations1);
				GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen texture 1");

				gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D_MULTISAMPLE, tex1Id, 0);
				GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c1");
			}
			else if (colorAttachmentRbo)
			{
				gl.glGenRenderbuffers(1, &rboId);
				gl.glBindRenderbuffer(GL_RENDERBUFFER, rboId);
				gl.glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples1, m_internalFormat, m_fboSize, m_fboSize);
				GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen rb");

				gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, rboId);
				GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c1");
			}
			else
				DE_ASSERT(DE_FALSE);

			// should not be complete
			{
				glw::GLenum status = gl.glCheckFramebufferStatus(GL_FRAMEBUFFER);

				if (status != GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
				{
					m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, got " << glu::getFramebufferStatusName(status) << tcu::TestLog::EndMessage;
					testFailed = true;
				}
			}
		}
	}
	catch (...)
	{
		gl.glDeleteFramebuffers(1, &fboId);
		gl.glDeleteRenderbuffers(1, &rboId);
		gl.glDeleteTextures(1, &tex0Id);
		gl.glDeleteTextures(1, &tex1Id);
		throw;
	}

	gl.glDeleteFramebuffers(1, &fboId);
	gl.glDeleteRenderbuffers(1, &rboId);
	gl.glDeleteTextures(1, &tex0Id);
	gl.glDeleteTextures(1, &tex1Id);

	if (testFailed)
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

void NegativeFramebufferCase::getFormatSamples (glw::GLenum target, std::vector<int>& samples)
{
	const glw::Functions	gl			= m_context.getRenderContext().getFunctions();
	int						sampleCount	= 0;

	gl.getInternalformativ(target, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &sampleCount);
	samples.resize(sampleCount);

	if (sampleCount > 0)
	{
		gl.getInternalformativ(target, m_internalFormat, GL_SAMPLES, sampleCount, &samples[0]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "get max samples");
	}
}

class NegativeTexParameterCase : public TestCase
{
public:
	enum TexParam
	{
		TEXTURE_MIN_FILTER = 0,
		TEXTURE_MAG_FILTER,
		TEXTURE_WRAP_S,
		TEXTURE_WRAP_T,
		TEXTURE_WRAP_R,
		TEXTURE_MIN_LOD,
		TEXTURE_MAX_LOD,
		TEXTURE_COMPARE_MODE,
		TEXTURE_COMPARE_FUNC,
		TEXTURE_BASE_LEVEL,

		TEXTURE_LAST
	};

					NegativeTexParameterCase	(Context& context, const char* name, const char* desc, TexParam param);
					~NegativeTexParameterCase	(void);

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

	glw::GLenum		getParamGLEnum				(void) const;
	glw::GLint		getParamValue				(void) const;
	glw::GLenum		getExpectedError			(void) const;

	const TexParam	m_texParam;
	int				m_iteration;
};

NegativeTexParameterCase::NegativeTexParameterCase (Context& context, const char* name, const char* desc, TexParam param)
	: TestCase		(context, name, desc)
	, m_texParam	(param)
	, m_iteration	(0)
{
	DE_ASSERT(param < TEXTURE_LAST);
}

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

void NegativeTexParameterCase::init (void)
{
	// default value
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
}

void NegativeTexParameterCase::deinit (void)
{
}

NegativeTexParameterCase::IterateResult NegativeTexParameterCase::iterate (void)
{
	static const struct TextureType
	{
		const char*	name;
		glw::GLenum	target;
		glw::GLenum	internalFormat;
		bool		isArrayType;
	} types[] =
	{
		{ "color",					GL_TEXTURE_2D_MULTISAMPLE,			GL_RGBA8,	false	},
		{ "color array",			GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_RGBA8,	true	},
		{ "signed integer",			GL_TEXTURE_2D_MULTISAMPLE,			GL_R8I,		false	},
		{ "signed integer array",	GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_R8I,		true	},
		{ "unsigned integer",		GL_TEXTURE_2D_MULTISAMPLE,			GL_R8UI,	false	},
		{ "unsigned integer array",	GL_TEXTURE_2D_MULTISAMPLE_ARRAY,	GL_R8UI,	true	},
	};

	const tcu::ScopedLogSection scope(m_testCtx.getLog(), "Iteration", std::string() + "Testing parameter with " + types[m_iteration].name + " texture");

	if (types[m_iteration].isArrayType && !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
		m_testCtx.getLog() << tcu::TestLog::Message << "GL_OES_texture_storage_multisample_2d_array not supported, skipping target" << tcu::TestLog::EndMessage;
	else
	{
		glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
		glu::Texture			texture	(m_context.getRenderContext());
		glw::GLenum				error;

		gl.enableLogging(true);

		// gen texture

		gl.glBindTexture(types[m_iteration].target, *texture);

		if (types[m_iteration].isArrayType)
			gl.glTexStorage3DMultisample(types[m_iteration].target, 1, types[m_iteration].internalFormat, 16, 16, 16, GL_FALSE);
		else
			gl.glTexStorage2DMultisample(types[m_iteration].target, 1, types[m_iteration].internalFormat, 16, 16, GL_FALSE);
		GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup texture");

		// set param

		gl.glTexParameteri(types[m_iteration].target, getParamGLEnum(), getParamValue());
		error = gl.glGetError();

		// expect failure

		if (error != getExpectedError())
		{
			m_testCtx.getLog() << tcu::TestLog::Message << "Got unexpected error: " << glu::getErrorStr(error) << ", expected: " << glu::getErrorStr(getExpectedError()) << tcu::TestLog::EndMessage;
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
		}
	}

	if (++m_iteration < DE_LENGTH_OF_ARRAY(types))
		return CONTINUE;
	return STOP;
}

glw::GLenum NegativeTexParameterCase::getParamGLEnum (void) const
{
	switch (m_texParam)
	{
		case TEXTURE_MIN_FILTER:	return GL_TEXTURE_MIN_FILTER;
		case TEXTURE_MAG_FILTER:	return GL_TEXTURE_MAG_FILTER;
		case TEXTURE_WRAP_S:		return GL_TEXTURE_WRAP_S;
		case TEXTURE_WRAP_T:		return GL_TEXTURE_WRAP_T;
		case TEXTURE_WRAP_R:		return GL_TEXTURE_WRAP_R;
		case TEXTURE_MIN_LOD:		return GL_TEXTURE_MIN_LOD;
		case TEXTURE_MAX_LOD:		return GL_TEXTURE_MAX_LOD;
		case TEXTURE_COMPARE_MODE:	return GL_TEXTURE_COMPARE_MODE;
		case TEXTURE_COMPARE_FUNC:	return GL_TEXTURE_COMPARE_FUNC;
		case TEXTURE_BASE_LEVEL:	return GL_TEXTURE_BASE_LEVEL;
		default:
			DE_ASSERT(DE_FALSE);
			return 0;
	}
}

glw::GLint NegativeTexParameterCase::getParamValue (void) const
{
	switch (m_texParam)
	{
		case TEXTURE_MIN_FILTER:	return GL_LINEAR;
		case TEXTURE_MAG_FILTER:	return GL_LINEAR;
		case TEXTURE_WRAP_S:		return GL_CLAMP_TO_EDGE;
		case TEXTURE_WRAP_T:		return GL_CLAMP_TO_EDGE;
		case TEXTURE_WRAP_R:		return GL_CLAMP_TO_EDGE;
		case TEXTURE_MIN_LOD:		return 1;
		case TEXTURE_MAX_LOD:		return 5;
		case TEXTURE_COMPARE_MODE:	return GL_NONE;
		case TEXTURE_COMPARE_FUNC:	return GL_NOTEQUAL;
		case TEXTURE_BASE_LEVEL:	return 2;
		default:
			DE_ASSERT(DE_FALSE);
			return 0;
	}
}

glw::GLenum NegativeTexParameterCase::getExpectedError (void) const
{
	switch (m_texParam)
	{
		case TEXTURE_MIN_FILTER:	return GL_INVALID_ENUM;
		case TEXTURE_MAG_FILTER:	return GL_INVALID_ENUM;
		case TEXTURE_WRAP_S:		return GL_INVALID_ENUM;
		case TEXTURE_WRAP_T:		return GL_INVALID_ENUM;
		case TEXTURE_WRAP_R:		return GL_INVALID_ENUM;
		case TEXTURE_MIN_LOD:		return GL_INVALID_ENUM;
		case TEXTURE_MAX_LOD:		return GL_INVALID_ENUM;
		case TEXTURE_COMPARE_MODE:	return GL_INVALID_ENUM;
		case TEXTURE_COMPARE_FUNC:	return GL_INVALID_ENUM;
		case TEXTURE_BASE_LEVEL:	return GL_INVALID_OPERATION;
		default:
			DE_ASSERT(DE_FALSE);
			return 0;
	}
}

class NegativeTexureSampleCase : public TestCase
{
public:
	enum SampleCountParam
	{
		SAMPLECOUNT_HIGH = 0,
		SAMPLECOUNT_ZERO,

		SAMPLECOUNT_LAST
	};

							NegativeTexureSampleCase	(Context& context, const char* name, const char* desc, SampleCountParam param);
private:
	IterateResult			iterate						(void);

	const SampleCountParam	m_sampleParam;
};

NegativeTexureSampleCase::NegativeTexureSampleCase (Context& context, const char* name, const char* desc, SampleCountParam param)
	: TestCase		(context, name, desc)
	, m_sampleParam	(param)
{
	DE_ASSERT(param < SAMPLECOUNT_LAST);
}

NegativeTexureSampleCase::IterateResult NegativeTexureSampleCase::iterate (void)
{
	const glw::GLenum		expectedError	= (m_sampleParam == SAMPLECOUNT_HIGH) ? (GL_INVALID_OPERATION) : (GL_INVALID_VALUE);
	glu::CallLogWrapper		gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	glu::Texture			texture			(m_context.getRenderContext());
	glw::GLenum				error;
	int						samples			= -1;

	gl.enableLogging(true);

	// calc samples

	if (m_sampleParam == SAMPLECOUNT_HIGH)
	{
		int maxSamples = 0;

		gl.glGetInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, 1, &maxSamples);
		GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetInternalformativ");

		samples = maxSamples + 1;
	}
	else if (m_sampleParam == SAMPLECOUNT_ZERO)
		samples = 0;
	else
		DE_ASSERT(DE_FALSE);

	// create texture with bad values

	gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, *texture);
	gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA8, 64, 64, GL_FALSE);
	error = gl.glGetError();

	// expect failure

	if (error == expectedError)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Got unexpected error: " << glu::getErrorStr(error) << ", expected: " << glu::getErrorStr(expectedError) << tcu::TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
	}

	return STOP;
}


} // anonymous

TextureMultisampleTests::TextureMultisampleTests (Context& context)
	: TestCaseGroup(context, "multisample", "Multisample texture tests")
{
}

TextureMultisampleTests::~TextureMultisampleTests (void)
{
}

void TextureMultisampleTests::init (void)
{
	static const int sampleCounts[] = { 1, 2, 3, 4, 8, 10, 12, 13, 16, 64 };

	static const struct TextureType
	{
		const char*									name;
		MultisampleTextureUsageCase::TextureType	type;
	} textureTypes[] =
	{
		{ "texture_color_2d",		MultisampleTextureUsageCase::TEXTURE_COLOR_2D		},
		{ "texture_color_2d_array",	MultisampleTextureUsageCase::TEXTURE_COLOR_2D_ARRAY	},
		{ "texture_int_2d",			MultisampleTextureUsageCase::TEXTURE_INT_2D			},
		{ "texture_int_2d_array",	MultisampleTextureUsageCase::TEXTURE_INT_2D_ARRAY	},
		{ "texture_uint_2d",		MultisampleTextureUsageCase::TEXTURE_UINT_2D		},
		{ "texture_uint_2d_array",	MultisampleTextureUsageCase::TEXTURE_UINT_2D_ARRAY	},
		{ "texture_depth_2d",		MultisampleTextureUsageCase::TEXTURE_DEPTH_2D		},
		{ "texture_depth_2d_array",	MultisampleTextureUsageCase::TEXTURE_DEPTH_2D_ARRAY	},
	};

	// .samples_x
	for (int sampleNdx = 0; sampleNdx < DE_LENGTH_OF_ARRAY(sampleCounts); ++sampleNdx)
	{
		tcu::TestCaseGroup* const sampleGroup = new tcu::TestCaseGroup(m_testCtx, (std::string("samples_") + de::toString(sampleCounts[sampleNdx])).c_str(), "Test with N samples");
		addChild(sampleGroup);

		// position query works
		sampleGroup->addChild(new SamplePosRasterizationTest(m_context, "sample_position", "test SAMPLE_POSITION", sampleCounts[sampleNdx]));

		// sample mask is ANDed properly
		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_only",											"Test with SampleMask only",									sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_NONE));
		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_alpha_to_coverage",						"Test with SampleMask and alpha to coverage",					sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_ALPHA_TO_COVERAGE));
		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_sample_coverage",							"Test with SampleMask and sample coverage",						sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_SAMPLE_COVERAGE));
		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_sample_coverage_and_alpha_to_coverage",	"Test with SampleMask, sample coverage, and alpha to coverage",	sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_ALPHA_TO_COVERAGE | SampleMaskCase::FLAGS_SAMPLE_COVERAGE));

		// high bits cause no unexpected behavior
		sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_non_effective_bits",							"Test with SampleMask, set higher bits than sample count",		sampleCounts[sampleNdx],	SampleMaskCase::FLAGS_HIGH_BITS));

		// usage
		for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(textureTypes); ++typeNdx)
			sampleGroup->addChild(new MultisampleTextureUsageCase(m_context, (std::string("use_") + textureTypes[typeNdx].name).c_str(), textureTypes[typeNdx].name, sampleCounts[sampleNdx], textureTypes[typeNdx].type));
	}

	// .negative
	{
		tcu::TestCaseGroup* const negativeGroup = new tcu::TestCaseGroup(m_testCtx, "negative", "Negative tests");
		addChild(negativeGroup);

		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_sample_count_tex_tex",	"Attach different sample counts",	NegativeFramebufferCase::CASE_DIFFERENT_N_SAMPLES_TEX));
		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_sample_count_tex_rbo",	"Attach different sample counts",	NegativeFramebufferCase::CASE_DIFFERENT_N_SAMPLES_RBO));
		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_fixed_state_tex_tex",		"Attach fixed and non fixed",		NegativeFramebufferCase::CASE_DIFFERENT_FIXED_TEX));
		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_different_fixed_state_tex_rbo",		"Attach fixed and non fixed",		NegativeFramebufferCase::CASE_DIFFERENT_FIXED_RBO));
		negativeGroup->addChild(new NegativeFramebufferCase	(m_context, "fbo_attach_non_zero_level",					"Attach non-zero level",			NegativeFramebufferCase::CASE_NON_ZERO_LEVEL));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_min_filter",							"set TEXTURE_MIN_FILTER",			NegativeTexParameterCase::TEXTURE_MIN_FILTER));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_mag_filter",							"set TEXTURE_MAG_FILTER",			NegativeTexParameterCase::TEXTURE_MAG_FILTER));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_s",								"set TEXTURE_WRAP_S",				NegativeTexParameterCase::TEXTURE_WRAP_S));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_t",								"set TEXTURE_WRAP_T",				NegativeTexParameterCase::TEXTURE_WRAP_T));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_r",								"set TEXTURE_WRAP_R",				NegativeTexParameterCase::TEXTURE_WRAP_R));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_min_lod",								"set TEXTURE_MIN_LOD",				NegativeTexParameterCase::TEXTURE_MIN_LOD));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_max_lod",								"set TEXTURE_MAX_LOD",				NegativeTexParameterCase::TEXTURE_MAX_LOD));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_compare_mode",							"set TEXTURE_COMPARE_MODE",			NegativeTexParameterCase::TEXTURE_COMPARE_MODE));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_compare_func",							"set TEXTURE_COMPARE_FUNC",			NegativeTexParameterCase::TEXTURE_COMPARE_FUNC));
		negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_base_level",							"set TEXTURE_BASE_LEVEL",			NegativeTexParameterCase::TEXTURE_BASE_LEVEL));
		negativeGroup->addChild(new NegativeTexureSampleCase(m_context, "texture_high_sample_count",					"TexStorage with high numSamples",	NegativeTexureSampleCase::SAMPLECOUNT_HIGH));
		negativeGroup->addChild(new NegativeTexureSampleCase(m_context, "texture_zero_sample_count",					"TexStorage with zero numSamples",	NegativeTexureSampleCase::SAMPLECOUNT_ZERO));
	}
}

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