C++程序  |  1034行  |  38.22 KB

/*-------------------------------------------------------------------------
 * 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 tests
 *//*--------------------------------------------------------------------*/

#include "es31fMultisampleTests.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuVector.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluObjectWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deString.h"
#include "deMath.h"

using namespace glw;

using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;

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;
}

class SamplePosQueryCase : public TestCase
{
public:
					SamplePosQueryCase (Context& context, const char* name, const char* desc);
private:
	void			init				(void);
	IterateResult	iterate				(void);
};

SamplePosQueryCase::SamplePosQueryCase (Context& context, const char* name, const char* desc)
	: TestCase(context, name, desc)
{
}

void SamplePosQueryCase::init (void)
{
	if (m_context.getRenderTarget().getNumSamples() == 0)
		throw tcu::NotSupportedError("No multisample buffers");
}

SamplePosQueryCase::IterateResult SamplePosQueryCase::iterate (void)
{
	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	bool				error	= false;

	gl.enableLogging(true);

	for (int ndx = 0; ndx < m_context.getRenderTarget().getNumSamples(); ++ndx)
	{
		tcu::Vec2 samplePos = tcu::Vec2(-1, -1);

		gl.glGetMultisamplefv(GL_SAMPLE_POSITION, ndx, samplePos.getPtr());
		GLU_EXPECT_NO_ERROR(gl.glGetError(), "getMultisamplefv");

		// check value range
		if (samplePos.x() < 0.0f || samplePos.x() > 1.0f ||
			samplePos.y() < 0.0f || samplePos.y() > 1.0f)
		{
			m_testCtx.getLog() << tcu::TestLog::Message << "Sample " << ndx << " is not in valid range [0,1], got " << samplePos << tcu::TestLog::EndMessage;
			error = true;
		}
	}

	if (!error)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid sample pos");

	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Abstract base class handling common stuff for default fbo multisample cases.
 *//*--------------------------------------------------------------------*/
class DefaultFBOMultisampleCase : public TestCase
{
public:
								DefaultFBOMultisampleCase	(Context& context, const char* name, const char* desc, int desiredViewportSize);
	virtual						~DefaultFBOMultisampleCase	(void);

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

protected:
	void						renderTriangle				(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
	void						renderTriangle				(const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const;
	void						renderTriangle				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const;
	void						renderTriangle				(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const;
	void						renderQuad					(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const;
	void						renderQuad					(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const;

	void						randomizeViewport			(void);
	void						readImage					(tcu::Surface& dst) const;

	int							m_numSamples;

	int							m_viewportSize;

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

	const int					m_desiredViewportSize;

	glu::ShaderProgram*			m_program;
	int							m_attrPositionLoc;
	int							m_attrColorLoc;

	int							m_viewportX;
	int							m_viewportY;
	de::Random					m_rnd;

	bool						m_initCalled;
};

DefaultFBOMultisampleCase::DefaultFBOMultisampleCase (Context& context, const char* name, const char* desc, int desiredViewportSize)
	: TestCase				(context, name, desc)
	, m_numSamples			(0)
	, m_viewportSize		(0)
	, m_desiredViewportSize	(desiredViewportSize)
	, m_program				(DE_NULL)
	, m_attrPositionLoc		(-1)
	, m_attrColorLoc		(-1)
	, m_viewportX			(0)
	, m_viewportY			(0)
	, m_rnd					(deStringHash(name))
	, m_initCalled			(false)
{
}

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

void DefaultFBOMultisampleCase::init (void)
{
	static const char* vertShaderSource =
		"#version 310 es\n"
		"in highp vec4 a_position;\n"
		"in mediump vec4 a_color;\n"
		"out mediump vec4 v_color;\n"
		"void main()\n"
		"{\n"
		"	gl_Position = a_position;\n"
		"	v_color = a_color;\n"
		"}\n";

	static const char* fragShaderSource =
		"#version 310 es\n"
		"in mediump vec4 v_color;\n"
		"layout(location = 0) out mediump vec4 o_color;\n"
		"void main()\n"
		"{\n"
		"	o_color = v_color;\n"
		"}\n";

	TestLog&				log	= m_testCtx.getLog();
	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();

	if (m_context.getRenderTarget().getNumSamples() <= 1)
		throw tcu::NotSupportedError("No multisample buffers");

	m_initCalled = true;

	// Query and log number of samples per pixel.

	gl.getIntegerv(GL_SAMPLES, &m_numSamples);
	GLU_EXPECT_NO_ERROR(gl.getError(), "getIntegerv(GL_SAMPLES)");
	log << TestLog::Message << "GL_SAMPLES = " << m_numSamples << TestLog::EndMessage;

	// Prepare program.

	DE_ASSERT(!m_program);

	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertShaderSource, fragShaderSource));
	if (!m_program->isOk())
		throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__);

	m_attrPositionLoc	= gl.getAttribLocation(m_program->getProgram(), "a_position");
	m_attrColorLoc		= gl.getAttribLocation(m_program->getProgram(), "a_color");
	GLU_EXPECT_NO_ERROR(gl.getError(), "getAttribLocation");

	if (m_attrPositionLoc < 0 || m_attrColorLoc < 0)
	{
		delete m_program;
		throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__);
	}

	// Get suitable viewport size.

	m_viewportSize = de::min<int>(m_desiredViewportSize, de::min(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight()));
	randomizeViewport();
}

void DefaultFBOMultisampleCase::deinit (void)
{
	// Do not try to call GL functions during case list creation
	if (!m_initCalled)
		return;

	delete m_program;
	m_program = DE_NULL;
}

void DefaultFBOMultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
{
	const float vertexPositions[] =
	{
		p0.x(), p0.y(), p0.z(), 1.0f,
		p1.x(), p1.y(), p1.z(), 1.0f,
		p2.x(), p2.y(), p2.z(), 1.0f
	};
	const float vertexColors[] =
	{
		c0.x(), c0.y(), c0.z(), c0.w(),
		c1.x(), c1.y(), c1.z(), c1.w(),
		c2.x(), c2.y(), c2.z(), c2.w(),
	};

	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
	glu::Buffer				vtxBuf	(m_context.getRenderContext());
	glu::Buffer				colBuf	(m_context.getRenderContext());
	glu::VertexArray		vao		(m_context.getRenderContext());

	gl.bindVertexArray(*vao);
	GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray");

	gl.bindBuffer(GL_ARRAY_BUFFER, *vtxBuf);
	gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), &vertexPositions[0], GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "vtx buf");

	gl.enableVertexAttribArray(m_attrPositionLoc);
	gl.vertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, DE_NULL);
	GLU_EXPECT_NO_ERROR(gl.getError(), "vtx vertexAttribPointer");

	gl.bindBuffer(GL_ARRAY_BUFFER, *colBuf);
	gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexColors), &vertexColors[0], GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "col buf");

	gl.enableVertexAttribArray(m_attrColorLoc);
	gl.vertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, DE_NULL);
	GLU_EXPECT_NO_ERROR(gl.getError(), "col vertexAttribPointer");

	gl.useProgram(m_program->getProgram());
	gl.drawArrays(GL_TRIANGLES, 0, 3);
	GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");
}

void DefaultFBOMultisampleCase::renderTriangle (const Vec3& p0, const Vec3& p1, const Vec3& p2, const Vec4& color) const
{
	renderTriangle(p0, p1, p2, color, color, color);
}

void DefaultFBOMultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& c0, const Vec4& c1, const Vec4& c2) const
{
	renderTriangle(Vec3(p0.x(), p0.y(), 0.0f),
				   Vec3(p1.x(), p1.y(), 0.0f),
				   Vec3(p2.x(), p2.y(), 0.0f),
				   c0, c1, c2);
}

void DefaultFBOMultisampleCase::renderTriangle (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec4& color) const
{
	renderTriangle(p0, p1, p2, color, color, color);
}

void DefaultFBOMultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& c0, const Vec4& c1, const Vec4& c2, const Vec4& c3) const
{
	renderTriangle(p0, p1, p2, c0, c1, c2);
	renderTriangle(p2, p1, p3, c2, c1, c3);
}

void DefaultFBOMultisampleCase::renderQuad (const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const Vec4& color) const
{
	renderQuad(p0, p1, p2, p3, color, color, color, color);
}

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

	m_viewportX = m_rnd.getInt(0, m_context.getRenderTarget().getWidth()  - m_viewportSize);
	m_viewportY = m_rnd.getInt(0, m_context.getRenderTarget().getHeight() - m_viewportSize);

	gl.viewport(m_viewportX, m_viewportY, m_viewportSize, m_viewportSize);
	GLU_EXPECT_NO_ERROR(gl.getError(), "viewport");
}

void DefaultFBOMultisampleCase::readImage (tcu::Surface& dst) const
{
	glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess());
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests coverage mask inversion validity.
 *
 * Tests that the coverage masks obtained by masks set with glSampleMaski(mask)
 * and glSampleMaski(~mask) are indeed each others' inverses.
 *
 * This is done by drawing a pattern, with varying coverage values,
 * overlapped by a pattern that has inverted masks and is otherwise
 * identical. The resulting image is compared to one obtained by drawing
 * the same pattern but with all-ones coverage masks.
 *//*--------------------------------------------------------------------*/
class MaskInvertCase : public DefaultFBOMultisampleCase
{
public:
					MaskInvertCase				(Context& context, const char* name, const char* description);
					~MaskInvertCase				(void) {}

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

private:
	void			drawPattern					(bool invert) const;
};

MaskInvertCase::MaskInvertCase (Context& context, const char* name, const char* description)
	: DefaultFBOMultisampleCase	(context, name, description, 256)
{
}

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

	// check the test is even possible

	GLint maxSampleMaskWords = 0;
	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
	if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

	// normal init
	DefaultFBOMultisampleCase::init();
}

MaskInvertCase::IterateResult MaskInvertCase::iterate (void)
{
	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();
	TestLog&				log								= m_testCtx.getLog();
	tcu::Surface			renderedImgNoSampleCoverage		(m_viewportSize, m_viewportSize);
	tcu::Surface			renderedImgSampleCoverage		(m_viewportSize, m_viewportSize);

	randomizeViewport();

	gl.enable(GL_BLEND);
	gl.blendEquation(GL_FUNC_ADD);
	gl.blendFunc(GL_ONE, GL_ONE);
	GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");
	log << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples" << TestLog::EndMessage;

	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
	gl.clearColor(0.0f, 0.0f, 0.0f, 0.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK disabled" << TestLog::EndMessage;
	drawPattern(false);
	readImage(renderedImgNoSampleCoverage);

	log << TestLog::Image("RenderedImageNoSampleMask", "Rendered image with GL_SAMPLE_MASK disabled", renderedImgNoSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);

	log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
	gl.clear(GL_COLOR_BUFFER_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

	gl.enable(GL_SAMPLE_MASK);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)");

	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using non-inverted sample masks" << TestLog::EndMessage;
	drawPattern(false);
	log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using inverted sample masks" << TestLog::EndMessage;
	drawPattern(true);

	readImage(renderedImgSampleCoverage);

	log << TestLog::Image("RenderedImageSampleMask", "Rendered image with GL_SAMPLE_MASK enabled", renderedImgSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);

	bool passed = tcu::pixelThresholdCompare(log,
											 "CoverageVsNoCoverage",
											 "Comparison of same pattern with GL_SAMPLE_MASK disabled and enabled",
											 renderedImgNoSampleCoverage,
											 renderedImgSampleCoverage,
											 tcu::RGBA(0),
											 tcu::COMPARE_LOG_ON_ERROR);

	if (passed)
		log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage;

	m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
											 passed ? "Passed"				: "Failed");

	return STOP;
}

void MaskInvertCase::drawPattern (bool invert) const
{
	const int				numTriangles	= 25;
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();

	for (int triNdx = 0; triNdx < numTriangles; triNdx++)
	{
		const float	angle0	= 2.0f*DE_PI * (float)triNdx			/ (float)numTriangles;
		const float	angle1	= 2.0f*DE_PI * (float)(triNdx + 0.5f)	/ (float)numTriangles;
		const Vec4	color	= Vec4(0.4f + (float)triNdx/(float)numTriangles*0.6f,
		                           0.5f + (float)triNdx/(float)numTriangles*0.3f,
		                           0.6f - (float)triNdx/(float)numTriangles*0.5f,
		                           0.7f - (float)triNdx/(float)numTriangles*0.7f);


		const int			wordCount		= getEffectiveSampleMaskWordCount(m_numSamples - 1);
		const GLbitfield	finalWordBits	= m_numSamples - 32 * ((m_numSamples-1) / 32);
		const GLbitfield	finalWordMask	= (GLbitfield)(1ULL << finalWordBits) - 1UL;

		for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
		{
			const GLbitfield	rawMask		= (GLbitfield)deUint32Hash(wordNdx * 32 + triNdx);
			const GLbitfield	mask		= (invert) ? (~rawMask) : (rawMask);
			const bool			isFinalWord	= (wordNdx + 1) == wordCount;
			const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count

			gl.sampleMaski(wordNdx, mask & maskMask);
		}
		GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");

		renderTriangle(Vec2(0.0f, 0.0f),
					   Vec2(deFloatCos(angle0)*0.95f, deFloatSin(angle0)*0.95f),
					   Vec2(deFloatCos(angle1)*0.95f, deFloatSin(angle1)*0.95f),
					   color);
	}
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests coverage mask generation proportionality property.
 *
 * Tests that the number of coverage bits in a coverage mask set with
 * glSampleMaski is, on average, proportional to the number of set bits.
 * Draws multiple frames, each time increasing the number of mask bits set
 * and checks that the average color is changing appropriately.
 *//*--------------------------------------------------------------------*/
class MaskProportionalityCase : public DefaultFBOMultisampleCase
{
public:
					MaskProportionalityCase				(Context& context, const char* name, const char* description);
					~MaskProportionalityCase			(void) {}

	void			init								(void);

	IterateResult	iterate								(void);

private:
	int				m_numIterations;
	int				m_currentIteration;

	deInt32			m_previousIterationColorSum;
};

MaskProportionalityCase::MaskProportionalityCase (Context& context, const char* name, const char* description)
	: DefaultFBOMultisampleCase		(context, name, description, 32)
	, m_numIterations				(-1)
	, m_currentIteration			(0)
	, m_previousIterationColorSum	(-1)
{
}

void MaskProportionalityCase::init (void)
{
	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
	TestLog&				log	= m_testCtx.getLog();

	// check the test is even possible
	GLint maxSampleMaskWords = 0;
	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
	if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

	DefaultFBOMultisampleCase::init();

	// set state
	gl.enable(GL_SAMPLE_MASK);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)");
	log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;

	m_numIterations = m_numSamples + 1;

	randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate.
}

MaskProportionalityCase::IterateResult MaskProportionalityCase::iterate (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	TestLog&				log				= m_testCtx.getLog();
	tcu::Surface			renderedImg		(m_viewportSize, m_viewportSize);
	deInt32					numPixels		= (deInt32)renderedImg.getWidth()*(deInt32)renderedImg.getHeight();

	DE_ASSERT(m_numIterations >= 0);

	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
	gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

	// Draw quad.

	{
		const Vec2					pt0						(-1.0f, -1.0f);
		const Vec2					pt1						( 1.0f, -1.0f);
		const Vec2					pt2						(-1.0f,  1.0f);
		const Vec2					pt3						( 1.0f,  1.0f);
		Vec4						quadColor				(1.0f, 0.0f, 0.0f, 1.0f);
		const std::vector<deUint32>	sampleMask				= genAllSetToNthBitSampleMask(m_currentIteration);

		DE_ASSERT(m_currentIteration <= m_numSamples + 1);

		log << TestLog::Message << "Drawing a red quad using sample mask 0b" << sampleMaskToString(sampleMask, m_numSamples) << TestLog::EndMessage;

		for (int wordNdx = 0; wordNdx < getEffectiveSampleMaskWordCount(m_numSamples - 1); ++wordNdx)
		{
			const GLbitfield mask = (wordNdx < (int)sampleMask.size()) ? ((GLbitfield)(sampleMask[wordNdx])) : (0);

			gl.sampleMaski(wordNdx, mask);
			GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
		}

		renderQuad(pt0, pt1, pt2, pt3, quadColor);
	}

	// Read ang log image.

	readImage(renderedImg);

	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);

	// Compute average red component in rendered image.

	deInt32 sumRed = 0;

	for (int y = 0; y < renderedImg.getHeight(); y++)
	for (int x = 0; x < renderedImg.getWidth(); x++)
		sumRed += renderedImg.getPixel(x, y).getRed();

	log << TestLog::Message << "Average red color component: " << de::floatToString((float)sumRed / 255.0f / (float)numPixels, 2) << TestLog::EndMessage;

	// Check if average color has decreased from previous frame's color.

	if (sumRed < m_previousIterationColorSum)
	{
		log << TestLog::Message << "Failure: Current average red color component is lower than previous" << TestLog::EndMessage;
		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
		return STOP;
	}

	// Check if coverage mask is not all-zeros if alpha or coverage value is 0 (or 1, if inverted).

	if (m_currentIteration == 0 && sumRed != 0)
	{
		log << TestLog::Message << "Failure: Image should be completely black" << TestLog::EndMessage;
		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
		return STOP;
	}

	if (m_currentIteration == m_numIterations-1 && sumRed != 0xff*numPixels)
	{
		log << TestLog::Message << "Failure: Image should be completely red" << TestLog::EndMessage;

		m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
		return STOP;
	}

	m_previousIterationColorSum = sumRed;

	m_currentIteration++;

	if (m_currentIteration >= m_numIterations)
	{
		log << TestLog::Message << "Success: Number of coverage mask bits set appears to be, on average, proportional to the number of set sample mask bits" << TestLog::EndMessage;
		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
		return STOP;
	}
	else
		return CONTINUE;
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests coverage mask generation constancy property.
 *
 * Tests that the coverage mask created by GL_SAMPLE_MASK is constant at
 * given pixel coordinates. Draws two quads, with the second one fully
 * overlapping the first one such that at any given pixel, both quads have
 * the same coverage mask value. This way, if the constancy property is
 * fulfilled, only the second quad should be visible.
 *//*--------------------------------------------------------------------*/
class MaskConstancyCase : public DefaultFBOMultisampleCase
{
public:
	enum CaseBits
	{
		CASEBIT_ALPHA_TO_COVERAGE			= 1,	//!< Use alpha-to-coverage.
		CASEBIT_SAMPLE_COVERAGE				= 2,	//!< Use sample coverage.
		CASEBIT_SAMPLE_COVERAGE_INVERTED	= 4,	//!< Inverted sample coverage.
		CASEBIT_SAMPLE_MASK					= 8,	//!< Use sample mask.
	};

					MaskConstancyCase			(Context& context, const char* name, const char* description, deUint32 typeBits);
					~MaskConstancyCase			(void) {}

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

private:
	const bool		m_isAlphaToCoverageCase;
	const bool		m_isSampleCoverageCase;
	const bool		m_isInvertedSampleCoverageCase;
	const bool		m_isSampleMaskCase;
};

MaskConstancyCase::MaskConstancyCase (Context& context, const char* name, const char* description, deUint32 typeBits)
	: DefaultFBOMultisampleCase			(context, name, description, 256)
	, m_isAlphaToCoverageCase			(0 != (typeBits & CASEBIT_ALPHA_TO_COVERAGE))
	, m_isSampleCoverageCase			(0 != (typeBits & CASEBIT_SAMPLE_COVERAGE))
	, m_isInvertedSampleCoverageCase	(0 != (typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED))
	, m_isSampleMaskCase				(0 != (typeBits & CASEBIT_SAMPLE_MASK))
{
	// CASEBIT_SAMPLE_COVERAGE_INVERT => CASEBIT_SAMPLE_COVERAGE
	DE_ASSERT((typeBits & CASEBIT_SAMPLE_COVERAGE) || ~(typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED));
	DE_ASSERT(m_isSampleMaskCase); // no point testing non-sample-mask cases, they are checked already in gles3
}

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

	// check the test is even possible
	if (m_isSampleMaskCase)
	{
		GLint maxSampleMaskWords = 0;
		gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
		if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
			throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");
	}

	// normal init
	DefaultFBOMultisampleCase::init();
}

MaskConstancyCase::IterateResult MaskConstancyCase::iterate (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	TestLog&				log				= m_testCtx.getLog();
	tcu::Surface			renderedImg		(m_viewportSize, m_viewportSize);

	randomizeViewport();

	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

	if (m_isAlphaToCoverageCase)
	{
		gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE);
		gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
		GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_ALPHA_TO_COVERAGE");

		log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage;
		log << TestLog::Message << "Color mask is TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage;
	}

	if (m_isSampleCoverageCase)
	{
		gl.enable(GL_SAMPLE_COVERAGE);
		GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_COVERAGE");

		log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage;
	}

	if (m_isSampleMaskCase)
	{
		gl.enable(GL_SAMPLE_MASK);
		GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK");

		log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;
	}

	log << TestLog::Message
		<< "Drawing several green quads, each fully overlapped by a red quad with the same "
		<< (m_isAlphaToCoverageCase ? "alpha" : "")
		<< (m_isAlphaToCoverageCase && (m_isSampleCoverageCase || m_isSampleMaskCase) ? " and " : "")
		<< (m_isInvertedSampleCoverageCase ? "inverted " : "")
		<< (m_isSampleCoverageCase ? "sample coverage" : "")
		<< (m_isSampleCoverageCase && m_isSampleMaskCase ? " and " : "")
		<< (m_isSampleMaskCase ? "sample mask" : "")
		<< " values"
		<< TestLog::EndMessage;

	const int numQuadRowsCols = m_numSamples*4;

	for (int row = 0; row < numQuadRowsCols; row++)
	{
		for (int col = 0; col < numQuadRowsCols; col++)
		{
			float		x0			= (float)(col+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
			float		x1			= (float)(col+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
			float		y0			= (float)(row+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
			float		y1			= (float)(row+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
			const Vec4	baseGreen	(0.0f, 1.0f, 0.0f, 0.0f);
			const Vec4	baseRed		(1.0f, 0.0f, 0.0f, 0.0f);
			Vec4		alpha0		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)col / (float)(numQuadRowsCols-1) : 1.0f);
			Vec4		alpha1		(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)row / (float)(numQuadRowsCols-1) : 1.0f);

			if (m_isSampleCoverageCase)
			{
				float value = (float)(row*numQuadRowsCols + col) / (float)(numQuadRowsCols*numQuadRowsCols-1);
				gl.sampleCoverage(m_isInvertedSampleCoverageCase ? 1.0f - value : value, m_isInvertedSampleCoverageCase ? GL_TRUE : GL_FALSE);
				GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleCoverage");
			}

			if (m_isSampleMaskCase)
			{
				const int			wordCount		= getEffectiveSampleMaskWordCount(m_numSamples - 1);
				const GLbitfield	finalWordBits	= m_numSamples - 32 * ((m_numSamples-1) / 32);
				const GLbitfield	finalWordMask	= (GLbitfield)(1ULL << finalWordBits) - 1UL;

				for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
				{
					const GLbitfield	mask		= (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
					const bool			isFinalWord	= (wordNdx + 1) == wordCount;
					const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count

					gl.sampleMaski(wordNdx, mask & maskMask);
					GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
				}
			}

			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen + alpha0,	baseGreen + alpha1,	baseGreen + alpha0,	baseGreen + alpha1);
			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed + alpha0,	baseRed + alpha1,	baseRed + alpha0,	baseRed + alpha1);
		}
	}

	readImage(renderedImg);

	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);

	for (int y = 0; y < renderedImg.getHeight(); y++)
	for (int x = 0; x < renderedImg.getWidth(); x++)
	{
		if (renderedImg.getPixel(x, y).getGreen() > 0)
		{
			log << TestLog::Message << "Failure: Non-zero green color component detected - should have been completely overwritten by red quad" << TestLog::EndMessage;
			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
			return STOP;
		}
	}

	log << TestLog::Message
		<< "Success: Coverage mask appears to be constant at a given pixel coordinate with a given "
		<< (m_isAlphaToCoverageCase ? "alpha" : "")
		<< (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "")
		<< (m_isSampleCoverageCase ? "coverage value" : "")
		<< TestLog::EndMessage;

	m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");

	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests that unused bits of a sample mask have no effect
 *
 * Tests that the bits in the sample mask with positions higher than
 * the number of samples do not have effect. In multisample fragment
 * operations the sample mask is ANDed with the fragment coverage value.
 * The coverage value cannot have the corresponding bits set.
 *
 * This is done by drawing a quads with varying sample masks and then
 * redrawing the quads with identical masks but with the mask's high bits
 * having different values. Only the latter quad pattern should be visible.
 *//*--------------------------------------------------------------------*/
class SampleMaskHighBitsCase : public DefaultFBOMultisampleCase
{
public:
					SampleMaskHighBitsCase		(Context& context, const char* name, const char* description);
					~SampleMaskHighBitsCase		(void) {}

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

SampleMaskHighBitsCase::SampleMaskHighBitsCase (Context& context, const char* name, const char* description)
	: DefaultFBOMultisampleCase(context, name, description, 256)
{
}

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

	// check the test is even possible
	gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
	if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
		throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

	// normal init
	DefaultFBOMultisampleCase::init();
}

SampleMaskHighBitsCase::IterateResult SampleMaskHighBitsCase::iterate (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	TestLog&				log				= m_testCtx.getLog();
	tcu::Surface			renderedImg		(m_viewportSize, m_viewportSize);
	de::Random				rnd				(12345);

	if (m_numSamples % 32 == 0)
	{
		log << TestLog::Message << "Sample count is multiple of word size. No unused high bits in sample mask.\nSkipping." << TestLog::EndMessage;
		m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Skipped");
		return STOP;
	}

	randomizeViewport();

	log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

	gl.enable(GL_SAMPLE_MASK);
	GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK");
	log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;
	log << TestLog::Message << "Drawing several green quads, each fully overlapped by a red quad with the same effective sample mask values" << TestLog::EndMessage;

	const int numQuadRowsCols = m_numSamples*4;

	for (int row = 0; row < numQuadRowsCols; row++)
	{
		for (int col = 0; col < numQuadRowsCols; col++)
		{
			float				x0				= (float)(col+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
			float				x1				= (float)(col+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
			float				y0				= (float)(row+0) / (float)numQuadRowsCols * 2.0f - 1.0f;
			float				y1				= (float)(row+1) / (float)numQuadRowsCols * 2.0f - 1.0f;
			const Vec4			baseGreen		(0.0f, 1.0f, 0.0f, 1.0f);
			const Vec4			baseRed			(1.0f, 0.0f, 0.0f, 1.0f);

			const int			wordCount		= getEffectiveSampleMaskWordCount(m_numSamples - 1);
			const GLbitfield	finalWordBits	= m_numSamples - 32 * ((m_numSamples-1) / 32);
			const GLbitfield	finalWordMask	= (GLbitfield)(1ULL << finalWordBits) - 1UL;

			for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
			{
				const GLbitfield	mask		= (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
				const bool			isFinalWord	= (wordNdx + 1) == wordCount;
				const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count
				const GLbitfield	highBits	= rnd.getUint32();

				gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask));
				GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
			}
			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen, baseGreen, baseGreen, baseGreen);

			for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
			{
				const GLbitfield	mask		= (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
				const bool			isFinalWord	= (wordNdx + 1) == wordCount;
				const GLbitfield	maskMask	= (isFinalWord) ? (finalWordMask) : (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count
				const GLbitfield	highBits	= rnd.getUint32();

				gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask));
				GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
			}
			renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed, baseRed, baseRed, baseRed);
		}
	}

	readImage(renderedImg);

	log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);

	for (int y = 0; y < renderedImg.getHeight(); y++)
	for (int x = 0; x < renderedImg.getWidth(); x++)
	{
		if (renderedImg.getPixel(x, y).getGreen() > 0)
		{
			log << TestLog::Message << "Failure: Non-zero green color component detected - should have been completely overwritten by red quad. Mask unused bits have effect." << TestLog::EndMessage;
			m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Unused mask bits modified mask");
			return STOP;
		}
	}

	log << TestLog::Message << "Success: Coverage mask high bits appear to have no effect." << TestLog::EndMessage;
	m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");

	return STOP;
}

} // anonymous

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

MultisampleTests::~MultisampleTests (void)
{
}

void MultisampleTests::init (void)
{
	tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "default_framebuffer", "Test with default framebuffer");

	addChild(group);

	// .default_framebuffer
	{
		// sample positions
		group->addChild(new SamplePosQueryCase			(m_context, "sample_position", "test SAMPLE_POSITION"));

		// sample mask
		group->addChild(new MaskInvertCase				(m_context, "sample_mask_sum_of_inverses",	"Test that mask and its negation's sum equal the fully set mask"));
		group->addChild(new MaskProportionalityCase		(m_context, "proportionality_sample_mask",	"Test the proportionality property of GL_SAMPLE_MASK"));

		group->addChild(new MaskConstancyCase			(m_context, "constancy_sample_mask",
																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_MASK",
																	MaskConstancyCase::CASEBIT_SAMPLE_MASK));
		group->addChild(new MaskConstancyCase			(m_context, "constancy_alpha_to_coverage_sample_mask",
																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and GL_SAMPLE_MASK",
																	MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
		group->addChild(new MaskConstancyCase			(m_context, "constancy_sample_coverage_sample_mask",
																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK",
																	MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
		group->addChild(new MaskConstancyCase			(m_context, "constancy_alpha_to_coverage_sample_coverage_sample_mask",
																	"Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE, GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK",
																	MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
		group->addChild(new SampleMaskHighBitsCase		(m_context, "sample_mask_non_effective_bits",
																	"Test that values of unused bits of a sample mask (bit index > sample count) have no effect"));
	}
}

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