/*-------------------------------------------------------------------------
 * 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 shader render case
 *//*--------------------------------------------------------------------*/

#include "es31fMultisampleShaderRenderCase.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "gluPixelTransfer.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deStringUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace MultisampleShaderRenderUtil
{
namespace
{

static const char* const s_vertexSource =	"#version 310 es\n"
											"in highp vec4 a_position;\n"
											"out highp vec4 v_position;\n"
											"void main (void)\n"
											"{\n"
											"	gl_Position = a_position;\n"
											"	v_position = a_position;\n"
											"}";

} // anonymous

QualityWarning::QualityWarning (const std::string& message)
	: tcu::Exception(message)
{
}

MultisampleRenderCase::MultisampleRenderCase (Context& context, const char* name, const char* desc, int numSamples, RenderTarget target, int renderSize, int flags)
	: TestCase						(context, name, desc)
	, m_numRequestedSamples			(numSamples)
	, m_renderTarget				(target)
	, m_renderSize					(renderSize)
	, m_perIterationShader			((flags & FLAG_PER_ITERATION_SHADER) != 0)
	, m_verifyTextureSampleBuffers	((flags & FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS) != 0 && target == TARGET_TEXTURE)
	, m_numTargetSamples			(-1)
	, m_buffer						(0)
	, m_resolveBuffer				(0)
	, m_program						(DE_NULL)
	, m_fbo							(0)
	, m_fboTexture					(0)
	, m_textureSamplerProgram		(DE_NULL)
	, m_fboRbo						(0)
	, m_resolveFbo					(0)
	, m_resolveFboTexture			(0)
	, m_iteration					(0)
	, m_numIterations				(1)
	, m_renderMode					(0)
	, m_renderCount					(0)
	, m_renderVao					(0)
	, m_resolveVao					(0)
{
	DE_ASSERT(target < TARGET_LAST);
}

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

void MultisampleRenderCase::init (void)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	deInt32					queriedSampleCount	= -1;

	// requirements

	switch (m_renderTarget)
	{
		case TARGET_DEFAULT:
		{
			if (m_context.getRenderTarget().getWidth() < m_renderSize || m_context.getRenderTarget().getHeight() < m_renderSize)
				throw tcu::NotSupportedError("Test requires render target with size " + de::toString(m_renderSize) + "x" + de::toString(m_renderSize) + " or greater");
			break;
		}

		case TARGET_TEXTURE:
		{
			deInt32 maxTextureSamples = 0;
			gl.getInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, 1, &maxTextureSamples);

			if (m_numRequestedSamples > maxTextureSamples)
				throw tcu::NotSupportedError("Sample count not supported");
			break;
		}

		case TARGET_RENDERBUFFER:
		{
			deInt32 maxRboSamples = 0;
			gl.getInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, 1, &maxRboSamples);

			if (m_numRequestedSamples > maxRboSamples)
				throw tcu::NotSupportedError("Sample count not supported");
			break;
		}

		default:
			DE_ASSERT(false);
	}

	// resources

	{
		gl.genBuffers(1, &m_buffer);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");

		setupRenderData();
		GLU_EXPECT_NO_ERROR(gl.getError(), "setup data");

		gl.genVertexArrays(1, &m_renderVao);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen vao");

		// buffer for MSAA texture resolving
		{
			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),
			};

			gl.genBuffers(1, &m_resolveBuffer);
			gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
			gl.bufferData(GL_ARRAY_BUFFER, (int)sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
			GLU_EXPECT_NO_ERROR(gl.getError(), "setup data");
		}
	}

	// msaa targets

	if (m_renderTarget == TARGET_TEXTURE)
	{
		const deUint32 textureTarget = (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);

		gl.genVertexArrays(1, &m_resolveVao);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen vao");

		gl.genTextures(1, &m_fboTexture);
		gl.bindTexture(textureTarget, m_fboTexture);
		if (m_numRequestedSamples == 0)
		{
			gl.texStorage2D(textureTarget, 1, GL_RGBA8, m_renderSize, m_renderSize);
			gl.texParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			gl.texParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		}
		else
			gl.texStorage2DMultisample(textureTarget, m_numRequestedSamples, GL_RGBA8, m_renderSize, m_renderSize, GL_FALSE);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");

		gl.genFramebuffers(1, &m_fbo);
		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureTarget, m_fboTexture, 0);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
			throw tcu::TestError("fbo not complete");

		if (m_numRequestedSamples != 0)
		{
			// for shader
			gl.getTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &queriedSampleCount);

			// logging
			m_testCtx.getLog() << tcu::TestLog::Message << "Asked for " << m_numRequestedSamples << " samples, got " << queriedSampleCount << " samples." << tcu::TestLog::EndMessage;

			// sanity
			if (queriedSampleCount < m_numRequestedSamples)
				throw tcu::TestError("Got less texture samples than asked for");
		}

		// texture sampler shader
		m_textureSamplerProgram = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_vertexSource) << glu::FragmentSource(genMSSamplerSource(queriedSampleCount)));
		if (!m_textureSamplerProgram->isOk())
		{
			m_testCtx.getLog() << tcu::TestLog::Section("SamplerShader", "Sampler shader") << *m_textureSamplerProgram << tcu::TestLog::EndSection;
			throw tcu::TestError("could not build program");
		}
	}
	else if (m_renderTarget == TARGET_RENDERBUFFER)
	{
		gl.genRenderbuffers(1, &m_fboRbo);
		gl.bindRenderbuffer(GL_RENDERBUFFER, m_fboRbo);
		gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numRequestedSamples, GL_RGBA8, m_renderSize, m_renderSize);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen rbo");

		gl.genFramebuffers(1, &m_fbo);
		gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_fboRbo);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
			throw tcu::TestError("fbo not complete");

		// logging
		gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &queriedSampleCount);
		m_testCtx.getLog() << tcu::TestLog::Message << "Asked for " << m_numRequestedSamples << " samples, got " << queriedSampleCount << " samples." << tcu::TestLog::EndMessage;

		// sanity
		if (queriedSampleCount < m_numRequestedSamples)
			throw tcu::TestError("Got less renderbuffer samples samples than asked for");
	}

	// fbo for resolving the multisample fbo
	if (m_renderTarget != TARGET_DEFAULT)
	{
		gl.genTextures(1, &m_resolveFboTexture);
		gl.bindTexture(GL_TEXTURE_2D, m_resolveFboTexture);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, m_renderSize, m_renderSize);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");

		gl.genFramebuffers(1, &m_resolveFbo);
		gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_resolveFboTexture, 0);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
			throw tcu::TestError("resolve fbo not complete");
	}

	// create verifier shader and set targetSampleCount

	{
		int realSampleCount = -1;

		if (m_renderTarget == TARGET_TEXTURE)
		{
			if (m_numRequestedSamples == 0)
				realSampleCount = 1; // non msaa texture
			else
				realSampleCount = de::max(1, queriedSampleCount); // msaa texture
		}
		else if (m_renderTarget == TARGET_RENDERBUFFER)
		{
			realSampleCount = de::max(1, queriedSampleCount); // msaa rbo
		}
		else if (m_renderTarget == TARGET_DEFAULT)
		{
			realSampleCount = de::max(1, m_context.getRenderTarget().getNumSamples());
		}
		else
			DE_ASSERT(DE_FALSE);

		// is set and is valid
		DE_ASSERT(realSampleCount != -1);
		DE_ASSERT(realSampleCount != 0);
		m_numTargetSamples = realSampleCount;
	}

	if (!m_perIterationShader)
	{
		m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource(m_numTargetSamples)) << glu::FragmentSource(genFragmentSource(m_numTargetSamples)));
		m_testCtx.getLog() << tcu::TestLog::Section("RenderShader", "Render shader") << *m_program << tcu::TestLog::EndSection;
		if (!m_program->isOk())
			throw tcu::TestError("could not build program");

	}
}

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

	if (m_resolveBuffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_resolveBuffer);
		m_resolveBuffer = 0;
	}

	delete m_program;
	m_program = DE_NULL;

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

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

	delete m_textureSamplerProgram;
	m_textureSamplerProgram = DE_NULL;

	if (m_fboRbo)
	{
		m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_fboRbo);
		m_fboRbo = 0;
	}

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

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

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

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

MultisampleRenderCase::IterateResult MultisampleRenderCase::iterate (void)
{
	// default value
	if (m_iteration == 0)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		preTest();
	}

	drawOneIteration();

	// next iteration
	++m_iteration;
	if (m_iteration < m_numIterations)
		return CONTINUE;
	else
	{
		postTest();
		return STOP;
	}
}

void MultisampleRenderCase::preDraw (void)
{
}

void MultisampleRenderCase::postDraw (void)
{
}

void MultisampleRenderCase::preTest (void)
{
}

void MultisampleRenderCase::postTest (void)
{
}

void MultisampleRenderCase::verifyResultImageAndSetResult (const tcu::Surface& resultImage)
{
	// verify using case-specific verification

	try
	{
		if (!verifyImage(resultImage))
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
	}
	catch (const QualityWarning& ex)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Quality warning, error = " << ex.what() << tcu::TestLog::EndMessage;

		// Failures are more important than warnings
		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, ex.what());
	}
}

void MultisampleRenderCase::verifyResultBuffersAndSetResult (const std::vector<tcu::Surface>& resultBuffers)
{
	// verify using case-specific verification

	try
	{
		if (!verifySampleBuffers(resultBuffers))
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
	}
	catch (const QualityWarning& ex)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "Quality warning, error = " << ex.what() << tcu::TestLog::EndMessage;

		// Failures are more important than warnings
		if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, ex.what());
	}
}

std::string	MultisampleRenderCase::getIterationDescription (int iteration) const
{
	DE_UNREF(iteration);
	DE_ASSERT(false);
	return "";
}

void MultisampleRenderCase::drawOneIteration (void)
{
	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();
	const std::string			sectionDescription	= (m_numIterations > 1) ? ("Iteration " + de::toString(m_iteration+1) + "/" + de::toString(m_numIterations) + ": " + getIterationDescription(m_iteration)) : ("Test");
	const tcu::ScopedLogSection	section				(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration), sectionDescription);

	// Per iteration shader?
	if (m_perIterationShader)
	{
		delete m_program;
		m_program = DE_NULL;

		m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource(m_numTargetSamples)) << glu::FragmentSource(genFragmentSource(m_numTargetSamples)));
		m_testCtx.getLog() << tcu::TestLog::Section("RenderShader", "Render shader") << *m_program << tcu::TestLog::EndSection;
		if (!m_program->isOk())
			throw tcu::TestError("could not build program");

	}

	// render
	{
		if (m_renderTarget == TARGET_TEXTURE || m_renderTarget == TARGET_RENDERBUFFER)
		{
			gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
			GLU_EXPECT_NO_ERROR(gl.getError(), "bind fbo");

			m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << m_renderSceneDescription << " with render shader to fbo." << tcu::TestLog::EndMessage;
		}
		else
			m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << m_renderSceneDescription << " with render shader to default framebuffer." << tcu::TestLog::EndMessage;

		gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
		gl.clear(GL_COLOR_BUFFER_BIT);
		gl.viewport(0, 0, m_renderSize, m_renderSize);
		GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

		gl.bindVertexArray(m_renderVao);
		gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);

		// set attribs
		DE_ASSERT(!m_renderAttribs.empty());
		for (std::map<std::string, Attrib>::const_iterator it = m_renderAttribs.begin(); it != m_renderAttribs.end(); ++it)
		{
			const deInt32 location = gl.getAttribLocation(m_program->getProgram(), it->first.c_str());

			if (location != -1)
			{
				gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, it->second.stride, (deUint8*)DE_NULL + it->second.offset);
				gl.enableVertexAttribArray(location);
			}
		}
		GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");

		gl.useProgram(m_program->getProgram());
		preDraw();
		gl.drawArrays(m_renderMode, 0, m_renderCount);
		postDraw();
		gl.useProgram(0);
		gl.bindVertexArray(0);
		GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

		if (m_renderTarget == TARGET_TEXTURE || m_renderTarget == TARGET_RENDERBUFFER)
			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
	}

	// read
	{
		if (m_renderTarget == TARGET_DEFAULT)
		{
			tcu::Surface resultImage(m_renderSize, m_renderSize);

			m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from default framebuffer." << tcu::TestLog::EndMessage;

			// default directly
			glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

			// set test result
			verifyResultImageAndSetResult(resultImage);
		}
		else if (m_renderTarget == TARGET_RENDERBUFFER)
		{
			tcu::Surface resultImage(m_renderSize, m_renderSize);

			// rbo by blitting to non-multisample fbo

			m_testCtx.getLog() << tcu::TestLog::Message << "Blitting result from fbo to single sample fbo. (Resolve multisample)" << tcu::TestLog::EndMessage;

			gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
			gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFbo);
			gl.blitFramebuffer(0, 0, m_renderSize, m_renderSize, 0, 0, m_renderSize, m_renderSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);
			GLU_EXPECT_NO_ERROR(gl.getError(), "blit resolve");

			m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from single sample framebuffer." << tcu::TestLog::EndMessage;

			gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_resolveFbo);
			glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);

			// set test result
			verifyResultImageAndSetResult(resultImage);
		}
		else if (m_renderTarget == TARGET_TEXTURE && !m_verifyTextureSampleBuffers)
		{
			const deInt32	posLocation		= gl.getAttribLocation(m_textureSamplerProgram->getProgram(), "a_position");
			const deInt32	samplerLocation	= gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampler");
			const deUint32	textureTarget	= (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);
			tcu::Surface	resultImage		(m_renderSize, m_renderSize);

			if (m_numRequestedSamples)
				m_testCtx.getLog() << tcu::TestLog::Message << "Using sampler shader to sample the multisample texture to single sample framebuffer." << tcu::TestLog::EndMessage;
			else
				m_testCtx.getLog() << tcu::TestLog::Message << "Drawing texture to single sample framebuffer. Using sampler shader." << tcu::TestLog::EndMessage;

			if (samplerLocation == -1)
				throw tcu::TestError("Location u_sampler was -1.");

			// resolve multisample texture by averaging
			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
			gl.clear(GL_COLOR_BUFFER_BIT);
			gl.viewport(0, 0, m_renderSize, m_renderSize);
			GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

			gl.bindVertexArray(m_resolveVao);
			gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
			gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
			gl.enableVertexAttribArray(posLocation);
			GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");

			gl.activeTexture(GL_TEXTURE0);
			gl.bindTexture(textureTarget, m_fboTexture);
			GLU_EXPECT_NO_ERROR(gl.getError(), "bind tex");

			gl.useProgram(m_textureSamplerProgram->getProgram());
			gl.uniform1i(samplerLocation, 0);

			gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
			gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);

			gl.useProgram(0);
			gl.bindVertexArray(0);
			GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

			m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from single sample framebuffer." << tcu::TestLog::EndMessage;

			glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);

			// set test result
			verifyResultImageAndSetResult(resultImage);
		}
		else if (m_renderTarget == TARGET_TEXTURE && m_verifyTextureSampleBuffers)
		{
			const deInt32				posLocation		= gl.getAttribLocation(m_textureSamplerProgram->getProgram(), "a_position");
			const deInt32				samplerLocation	= gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampler");
			const deInt32				sampleLocation	= gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampleNdx");
			const deUint32				textureTarget	= (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);
			std::vector<tcu::Surface>	resultBuffers	(m_numTargetSamples);

			if (m_numRequestedSamples)
				m_testCtx.getLog() << tcu::TestLog::Message << "Reading multisample texture sample buffers." << tcu::TestLog::EndMessage;
			else
				m_testCtx.getLog() << tcu::TestLog::Message << "Reading texture." << tcu::TestLog::EndMessage;

			if (samplerLocation == -1)
				throw tcu::TestError("Location u_sampler was -1.");
			if (sampleLocation == -1)
				throw tcu::TestError("Location u_sampleNdx was -1.");

			for (int sampleNdx = 0; sampleNdx < m_numTargetSamples; ++sampleNdx)
				resultBuffers[sampleNdx].setSize(m_renderSize, m_renderSize);

			// read sample buffers to different surfaces
			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
			gl.clear(GL_COLOR_BUFFER_BIT);
			gl.viewport(0, 0, m_renderSize, m_renderSize);
			GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

			gl.bindVertexArray(m_resolveVao);
			gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
			gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
			gl.enableVertexAttribArray(posLocation);
			GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");

			gl.activeTexture(GL_TEXTURE0);
			gl.bindTexture(textureTarget, m_fboTexture);
			GLU_EXPECT_NO_ERROR(gl.getError(), "bind tex");

			gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
			gl.useProgram(m_textureSamplerProgram->getProgram());
			gl.uniform1i(samplerLocation, 0);

			m_testCtx.getLog() << tcu::TestLog::Message << "Reading sample buffers" << tcu::TestLog::EndMessage;

			for (int sampleNdx = 0; sampleNdx < m_numTargetSamples; ++sampleNdx)
			{
				gl.uniform1i(sampleLocation, sampleNdx);
				gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
				GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

				glu::readPixels(m_context.getRenderContext(), 0, 0, resultBuffers[sampleNdx].getAccess());
				GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
			}

			gl.useProgram(0);
			gl.bindVertexArray(0);
			gl.bindFramebuffer(GL_FRAMEBUFFER, 0);

			// verify sample buffers
			verifyResultBuffersAndSetResult(resultBuffers);
		}
		else
			DE_ASSERT(false);
	}
}

std::string	MultisampleRenderCase::genVertexSource (int numTargetSamples) const
{
	DE_UNREF(numTargetSamples);
	return std::string(s_vertexSource);
}

std::string MultisampleRenderCase::genMSSamplerSource (int numTargetSamples) const
{
	if (m_verifyTextureSampleBuffers)
		return genMSTextureLayerFetchSource(numTargetSamples);
	else
		return genMSTextureResolverSource(numTargetSamples);
}

std::string	MultisampleRenderCase::genMSTextureResolverSource (int numTargetSamples) const
{
	// default behavior: average

	const bool			isSingleSampleTarget = (m_numRequestedSamples == 0);
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in mediump vec4 v_position;\n"
			"layout(location = 0) out mediump vec4 fragColor;\n"
			"uniform mediump " << ((isSingleSampleTarget) ? ("sampler2D") : ("sampler2DMS")) << " u_sampler;\n"
			"void main (void)\n"
			"{\n"
			"	mediump vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
			"	mediump ivec2 fetchPos = ivec2(floor(relPosition * " << m_renderSize << ".0));\n"
			"	mediump vec4 colorSum = vec4(0.0, 0.0, 0.0, 0.0);\n"
			"\n";

	if (isSingleSampleTarget)
		buf <<	"	colorSum = texelFetch(u_sampler, fetchPos, 0);\n"
				"\n";
	else
		buf <<	"	for (int sampleNdx = 0; sampleNdx < " << numTargetSamples << "; ++sampleNdx)\n"
				"		colorSum += texelFetch(u_sampler, fetchPos, sampleNdx);\n"
				"	colorSum /= " << numTargetSamples << ".0;\n"
				"\n";

	buf <<	"	fragColor = vec4(colorSum.xyz, 1.0);\n"
			"}\n";

	return buf.str();
}

std::string MultisampleRenderCase::genMSTextureLayerFetchSource (int numTargetSamples) const
{
	DE_UNREF(numTargetSamples);

	const bool			isSingleSampleTarget = (m_numRequestedSamples == 0);
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in mediump vec4 v_position;\n"
			"layout(location = 0) out mediump vec4 fragColor;\n"
			"uniform mediump " << ((isSingleSampleTarget) ? ("sampler2D") : ("sampler2DMS")) << " u_sampler;\n"
			"uniform mediump int u_sampleNdx;\n"
			"void main (void)\n"
			"{\n"
			"	mediump vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
			"	mediump ivec2 fetchPos = ivec2(floor(relPosition * " << m_renderSize << ".0));\n"
			"\n"
			"	mediump vec4 color = texelFetch(u_sampler, fetchPos, u_sampleNdx);\n"
			"	fragColor = vec4(color.rgb, 1.0);\n"
			"}\n";

	return buf.str();
}

bool MultisampleRenderCase::verifySampleBuffers (const std::vector<tcu::Surface>& resultBuffers)
{
	DE_UNREF(resultBuffers);
	DE_ASSERT(false);
	return false;
}

void MultisampleRenderCase::setupRenderData (void)
{
	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();

	m_renderMode = GL_TRIANGLE_STRIP;
	m_renderCount = 4;
	m_renderSceneDescription = "quad";

	m_renderAttribs["a_position"].offset = 0;
	m_renderAttribs["a_position"].stride = sizeof(float[4]);

	gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
	gl.bufferData(GL_ARRAY_BUFFER, (int)sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
}

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