/*-------------------------------------------------------------------------
 * 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 Stencil texturing tests.
 *//*--------------------------------------------------------------------*/

#include "es31fStencilTexturingTests.hpp"

#include "gluStrUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluDrawUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"

#include "glsTextureTestUtil.hpp"

#include "tcuVector.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuTexLookupVerifier.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include "deStringUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{

using std::vector;
using std::string;
using tcu::IVec4;
using tcu::Vec2;
using tcu::Vec4;
using tcu::TestLog;
using tcu::TextureLevel;
using tcu::TextureFormat;

namespace
{

static void genTestRects (vector<IVec4>& rects, int width, int height)
{
	int curWidth	= width;
	int curHeight	= height;
	int ndx			= 0;

	for (;;)
	{
		rects.push_back(IVec4(width-curWidth, height-curHeight, curWidth, curHeight));

		DE_ASSERT(curWidth >= 1 && curHeight >= 1);
		if (curWidth == 1 && curHeight == 1)
			break;
		else if (curHeight > 1 && ((ndx%2) == 0 || curWidth == 1))
			curHeight -= 1;
		else
			curWidth -= 1;

		ndx += 1;
	}
}

static void rectsToTriangles (const vector<IVec4>& rects, int width, int height, vector<Vec2>& positions, vector<deUint16>& indices)
{
	const float		w		= float(width);
	const float		h		= float(height);

	positions.resize(rects.size()*4);
	indices.resize(rects.size()*6);

	for (int rectNdx = 0; rectNdx < (int)rects.size(); rectNdx++)
	{
		const int		rx		= rects[rectNdx].x();
		const int		ry		= rects[rectNdx].y();
		const int		rw		= rects[rectNdx].z();
		const int		rh		= rects[rectNdx].w();

		const float		x0		= float(rx*2)/w - 1.0f;
		const float		x1		= float((rx+rw)*2)/w - 1.0f;
		const float		y0		= float(ry*2)/h - 1.0f;
		const float		y1		= float((ry+rh)*2)/h - 1.0f;

		positions[rectNdx*4 + 0] = Vec2(x0, y0);
		positions[rectNdx*4 + 1] = Vec2(x1, y0);
		positions[rectNdx*4 + 2] = Vec2(x0, y1);
		positions[rectNdx*4 + 3] = Vec2(x1, y1);

		indices[rectNdx*6 + 0] = (deUint16)(rectNdx*4 + 0);
		indices[rectNdx*6 + 1] = (deUint16)(rectNdx*4 + 1);
		indices[rectNdx*6 + 2] = (deUint16)(rectNdx*4 + 2);
		indices[rectNdx*6 + 3] = (deUint16)(rectNdx*4 + 2);
		indices[rectNdx*6 + 4] = (deUint16)(rectNdx*4 + 1);
		indices[rectNdx*6 + 5] = (deUint16)(rectNdx*4 + 3);
	}
}

static void drawTestPattern (const glu::RenderContext& renderCtx, int width, int height)
{
	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
		<< glu::VertexSource(
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"}\n")
		<< glu::FragmentSource(
			"#version 300 es\n"
			"void main (void) {}\n"));

	const glw::Functions&	gl		= renderCtx.getFunctions();
	vector<IVec4>			rects;
	vector<Vec2>			positions;
	vector<deUint16>		indices;

	if (!program.isOk())
		throw tcu::TestError("Compile failed");

	gl.useProgram	(program.getProgram());
	gl.viewport		(0, 0, width, height);
	gl.clear		(GL_STENCIL_BUFFER_BIT);
	gl.enable		(GL_STENCIL_TEST);
	gl.stencilOp	(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
	gl.stencilFunc	(GL_ALWAYS, 0, ~0u);
	GLU_EXPECT_NO_ERROR(gl.getError(), "State setup failed");

	genTestRects	(rects, width, height);
	rectsToTriangles(rects, width, height, positions, indices);

	{
		const glu::VertexArrayBinding posBinding = glu::va::Float("a_position", 2, (int)positions.size(), 0, positions[0].getPtr());
		glu::draw(renderCtx, program.getProgram(), 1, &posBinding, glu::pr::Triangles((int)indices.size(), &indices[0]));
	}

	gl.disable(GL_STENCIL_TEST);
}

static void renderTestPatternReference (const tcu::PixelBufferAccess& dst)
{
	const int		stencilBits		= tcu::getTextureFormatBitDepth(tcu::getEffectiveDepthStencilAccess(dst, tcu::Sampler::MODE_STENCIL).getFormat()).x();
	const deUint32	stencilMask		= (1u<<stencilBits)-1u;
	vector<IVec4>	rects;

	DE_ASSERT(dst.getFormat().order == TextureFormat::S || dst.getFormat().order == TextureFormat::DS);

	// clear depth and stencil
	if (dst.getFormat().order == TextureFormat::DS)
		tcu::clearDepth(dst, 0.0f);
	tcu::clearStencil(dst, 0u);

	genTestRects(rects, dst.getWidth(), dst.getHeight());

	for (vector<IVec4>::const_iterator rectIter = rects.begin(); rectIter != rects.end(); ++rectIter)
	{
		const int	x0		= rectIter->x();
		const int	y0		= rectIter->y();
		const int	x1		= x0+rectIter->z();
		const int	y1		= y0+rectIter->w();

		for (int y = y0; y < y1; y++)
		{
			for (int x = x0; x < x1; x++)
			{
				const int oldVal	= dst.getPixStencil(x, y);
				const int newVal	= (oldVal+1)&stencilMask;

				dst.setPixStencil(newVal, x, y);
			}
		}
	}
}

static void blitStencilToColor2D (const glu::RenderContext& renderCtx, deUint32 srcTex, int width, int height)
{
	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
		<< glu::VertexSource(
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"in highp vec2 a_texCoord;\n"
			"out highp vec2 v_texCoord;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"	v_texCoord = a_texCoord;\n"
			"}\n")
		<< glu::FragmentSource(
			"#version 300 es\n"
			"uniform highp usampler2D u_sampler;\n"
			"in highp vec2 v_texCoord;\n"
			"layout(location = 0) out highp uint o_stencil;\n"
			"void main (void)\n"
			"{\n"
			"	o_stencil = texture(u_sampler, v_texCoord).x;\n"
			"}\n"));

	const float positions[] =
	{
		-1.0f, -1.0f,
		+1.0f, -1.0f,
		-1.0f, +1.0f,
		+1.0f, +1.0f
	};
	const float texCoord[] =
	{
		0.0f, 0.0f,
		1.0f, 0.0f,
		0.0f, 1.0f,
		1.0f, 1.0f
	};
	const glu::VertexArrayBinding vertexArrays[] =
	{
		glu::va::Float("a_position", 2, 4, 0, &positions[0]),
		glu::va::Float("a_texCoord", 2, 4, 0, &texCoord[0])
	};
	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };

	const glw::Functions& gl = renderCtx.getFunctions();

	if (!program.isOk())
		throw tcu::TestError("Compile failed");

	gl.activeTexture(GL_TEXTURE0);
	gl.bindTexture(GL_TEXTURE_2D, srcTex);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");

	gl.useProgram(program.getProgram());
	gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");

	gl.viewport(0, 0, width, height);
	glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}

static void blitStencilToColor2DArray (const glu::RenderContext& renderCtx, deUint32 srcTex, int width, int height, int level)
{
	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
		<< glu::VertexSource(
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"in highp vec3 a_texCoord;\n"
			"out highp vec3 v_texCoord;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"	v_texCoord = a_texCoord;\n"
			"}\n")
		<< glu::FragmentSource(
			"#version 300 es\n"
			"uniform highp usampler2DArray u_sampler;\n"
			"in highp vec3 v_texCoord;\n"
			"layout(location = 0) out highp uint o_stencil;\n"
			"void main (void)\n"
			"{\n"
			"	o_stencil = texture(u_sampler, v_texCoord).x;\n"
			"}\n"));

	const float positions[] =
	{
		-1.0f, -1.0f,
		+1.0f, -1.0f,
		-1.0f, +1.0f,
		+1.0f, +1.0f
	};
	const float texCoord[] =
	{
		0.0f, 0.0f, float(level),
		1.0f, 0.0f, float(level),
		0.0f, 1.0f, float(level),
		1.0f, 1.0f, float(level)
	};
	const glu::VertexArrayBinding vertexArrays[] =
	{
		glu::va::Float("a_position", 2, 4, 0, &positions[0]),
		glu::va::Float("a_texCoord", 3, 4, 0, &texCoord[0])
	};
	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };

	const glw::Functions& gl = renderCtx.getFunctions();

	if (!program.isOk())
		throw tcu::TestError("Compile failed");

	gl.activeTexture(GL_TEXTURE0);
	gl.bindTexture(GL_TEXTURE_2D_ARRAY, srcTex);
	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");

	gl.useProgram(program.getProgram());
	gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");

	gl.viewport(0, 0, width, height);
	glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}

static void blitStencilToColorCube (const glu::RenderContext& renderCtx, deUint32 srcTex, const float* texCoord, int width, int height)
{
	const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
		<< glu::VertexSource(
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"in highp vec3 a_texCoord;\n"
			"out highp vec3 v_texCoord;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"	v_texCoord = a_texCoord;\n"
			"}\n")
		<< glu::FragmentSource(
			"#version 300 es\n"
			"uniform highp usamplerCube u_sampler;\n"
			"in highp vec3 v_texCoord;\n"
			"layout(location = 0) out highp vec4 o_color;\n"
			"void main (void)\n"
			"{\n"
			"	o_color.x = float(texture(u_sampler, v_texCoord).x) / 255.0;\n"
			"	o_color.yzw = vec3(0.0, 0.0, 1.0);\n"
			"}\n"));

	const float positions[] =
	{
		-1.0f, -1.0f,
		-1.0f, +1.0f,
		+1.0f, -1.0f,
		+1.0f, +1.0f
	};
	const glu::VertexArrayBinding vertexArrays[] =
	{
		glu::va::Float("a_position", 2, 4, 0, &positions[0]),
		glu::va::Float("a_texCoord", 3, 4, 0, texCoord)
	};
	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };

	const glw::Functions& gl = renderCtx.getFunctions();

	if (!program.isOk())
		throw tcu::TestError("Compile failed");

	gl.activeTexture(GL_TEXTURE0);
	gl.bindTexture(GL_TEXTURE_CUBE_MAP, srcTex);
	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");

	gl.useProgram(program.getProgram());
	gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");

	gl.viewport(0, 0, width, height);
	glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
			  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}

static inline tcu::ConstPixelBufferAccess stencilToRedAccess (const tcu::ConstPixelBufferAccess& access)
{
	DE_ASSERT(access.getFormat() == TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8));
	return tcu::ConstPixelBufferAccess(TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT8), access.getSize(), access.getPitch(), access.getDataPtr());
}

static bool compareStencilToRed (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& stencilRef, const tcu::ConstPixelBufferAccess& result)
{
	const int		maxPrints		= 10;
	int				numFailed		= 0;

	DE_ASSERT(stencilRef.getFormat().order == TextureFormat::S);
	DE_ASSERT(stencilRef.getWidth() == result.getWidth() && stencilRef.getHeight() == result.getHeight());

	for (int y = 0; y < stencilRef.getHeight(); y++)
	{
		for (int x = 0; x < stencilRef.getWidth(); x++)
		{
			const int		ref		= stencilRef.getPixStencil(x, y);
			const int		res		= result.getPixelInt(x, y).x();

			if (ref != res)
			{
				if (numFailed < maxPrints)
					log << TestLog::Message << "ERROR: Expected " << ref << ", got " << res << " at (" << x << ", " << y << ")" << TestLog::EndMessage;
				else if (numFailed == maxPrints)
					log << TestLog::Message << "..." << TestLog::EndMessage;

				numFailed += 1;
			}
		}
	}

	log << TestLog::Message << "Found " << numFailed << " faulty pixels, comparison " << (numFailed == 0 ? "passed." : "FAILED!") << TestLog::EndMessage;

	log << TestLog::ImageSet("ComparisonResult", "Image comparison result")
		<< TestLog::Image("Result", "Result stencil buffer", result);

	if (numFailed > 0)
		log << TestLog::Image("Reference", "Reference stencil buffer", stencilToRedAccess(stencilRef));

	log << TestLog::EndImageSet;

	return numFailed == 0;
}

static bool compareRedChannel (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& result, int reference)
{
	const int		maxPrints		= 10;
	int				numFailed		= 0;

	for (int y = 0; y < result.getHeight(); y++)
	{
		for (int x = 0; x < result.getWidth(); x++)
		{
			const int res = result.getPixelInt(x, y).x();

			if (reference != res)
			{
				if (numFailed < maxPrints)
					log << TestLog::Message << "ERROR: Expected " << reference << ", got " << res << " at (" << x << ", " << y << ")" << TestLog::EndMessage;
				else if (numFailed == maxPrints)
					log << TestLog::Message << "..." << TestLog::EndMessage;

				numFailed += 1;
			}
		}
	}

	log << TestLog::Message << "Found " << numFailed << " faulty pixels, comparison " << (numFailed == 0 ? "passed." : "FAILED!") << TestLog::EndMessage;

	log << TestLog::ImageSet("ComparisonResult", "Image comparison result")
		<< TestLog::Image("Result", "Result stencil buffer", result);

	log << TestLog::EndImageSet;

	return numFailed == 0;
}

static void stencilToUnorm8 (const tcu::TextureCube& src, tcu::TextureCube& dst)
{
	for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
	{
		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
		{
			const tcu::CubeFace face = tcu::CubeFace(faceNdx);

			if (!src.isLevelEmpty(face, levelNdx))
			{
				dst.allocLevel(face, levelNdx);

				const tcu::ConstPixelBufferAccess	srcLevel	= src.getLevelFace(levelNdx, face);
				const tcu::PixelBufferAccess		dstLevel	= dst.getLevelFace(levelNdx, face);

				for (int y = 0; y < src.getSize(); y++)
				for (int x = 0; x < src.getSize(); x++)
					dstLevel.setPixel(Vec4(float(srcLevel.getPixStencil(x, y)) / 255.f, 0.f, 0.f, 1.f), x, y);
			}
		}
	}
}

static void checkDepthStencilFormatSupport (const glu::ContextInfo& ctxInfo, deUint32 format)
{
	if (format == GL_STENCIL_INDEX8)
	{
		const char* reqExt = "GL_OES_texture_stencil8";
		if (!ctxInfo.isExtensionSupported(reqExt))
			throw tcu::NotSupportedError(glu::getTextureFormatStr(format).toString() + " requires " + reqExt);
	}
	else
	{
		DE_ASSERT(format == GL_DEPTH32F_STENCIL8 || format == GL_DEPTH24_STENCIL8);
	}
}

static void checkFramebufferStatus (const glw::Functions& gl)
{
	const deUint32 status = gl.checkFramebufferStatus(GL_FRAMEBUFFER);

	if (status == GL_FRAMEBUFFER_UNSUPPORTED)
		throw tcu::NotSupportedError("Unsupported framebuffer configuration");
	else if (status != GL_FRAMEBUFFER_COMPLETE)
		throw tcu::TestError("Incomplete framebuffer: " + glu::getFramebufferStatusStr(status).toString());
}

class UploadTex2DCase : public TestCase
{
public:
	UploadTex2DCase (Context& context, const char* name, deUint32 format)
		: TestCase	(context, name, glu::getTextureFormatName(format))
		, m_format	(format)
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
		const glw::Functions&		gl					= renderCtx.getFunctions();
		const int					width				= 129;
		const int					height				= 113;
		glu::Framebuffer			fbo					(renderCtx);
		glu::Renderbuffer			colorBuf			(renderCtx);
		glu::Texture				depthStencilTex		(renderCtx);
		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), width, height);
		TextureLevel				readLevel			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
		TextureLevel				stencilOnlyLevel	(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);

		renderTestPatternReference(uploadLevel);
		renderTestPatternReference(stencilOnlyLevel);

		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
		glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, 0, 0, 0, uploadLevel);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
		checkFramebufferStatus(gl);

		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
		glu::readPixels(renderCtx, 0, 0, readLevel);

		{
			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
									compareOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}

private:
	const deUint32 m_format;
};

class UploadTex2DArrayCase : public TestCase
{
public:
	UploadTex2DArrayCase (Context& context, const char* name, deUint32 format)
		: TestCase	(context, name, glu::getTextureFormatName(format))
		, m_format	(format)
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
		const glw::Functions&		gl					= renderCtx.getFunctions();
		const int					width				= 41;
		const int					height				= 13;
		const int					levels				= 7;
		const int					ptrnLevel			= 3;
		glu::Framebuffer			fbo					(renderCtx);
		glu::Renderbuffer			colorBuf			(renderCtx);
		glu::Texture				depthStencilTex		(renderCtx);
		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), width, height, levels);

		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);

		for (int levelNdx = 0; levelNdx < levels; levelNdx++)
		{
			const tcu::PixelBufferAccess levelAccess = tcu::getSubregion(uploadLevel.getAccess(), 0, 0, levelNdx, width, height, 1);

			if (levelNdx == ptrnLevel)
				renderTestPatternReference(levelAccess);
			else
				tcu::clearStencil(levelAccess, levelNdx);
		}

		gl.bindTexture(GL_TEXTURE_2D_ARRAY, *depthStencilTex);
		gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, m_format, width, height, levels);
		glu::texSubImage3D(renderCtx, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, uploadLevel);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
		checkFramebufferStatus(gl);

		{
			TextureLevel	readLevel		(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
			bool			allLevelsOk		= true;

			for (int levelNdx = 0; levelNdx < levels; levelNdx++)
			{
				tcu::ScopedLogSection section(m_testCtx.getLog(), "Level" + de::toString(levelNdx), "Level " + de::toString(levelNdx));
				bool levelOk;

				blitStencilToColor2DArray(renderCtx, *depthStencilTex, width, height, levelNdx);
				glu::readPixels(renderCtx, 0, 0, readLevel);

				if (levelNdx == ptrnLevel)
				{
					TextureLevel reference(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);
					renderTestPatternReference(reference);

					levelOk = compareStencilToRed(m_testCtx.getLog(), reference, readLevel);
				}
				else
					levelOk = compareRedChannel(m_testCtx.getLog(), readLevel, levelNdx);

				if (!levelOk)
				{
					allLevelsOk = false;
					break;
				}
			}

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

		return STOP;
	}

private:
	const deUint32 m_format;
};

class UploadTexCubeCase : public TestCase
{
public:
	UploadTexCubeCase (Context& context, const char* name, deUint32 format)
		: TestCase	(context, name, glu::getTextureFormatName(format))
		, m_format	(format)
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
		const glw::Functions&		gl					= renderCtx.getFunctions();
		const int					size				= 64;
		const int					renderWidth			= 128;
		const int					renderHeight		= 128;
		vector<float>				texCoord;
		glu::Framebuffer			fbo					(renderCtx);
		glu::Renderbuffer			colorBuf			(renderCtx);
		glu::Texture				depthStencilTex		(renderCtx);
		tcu::TextureCube			texData				(glu::mapGLInternalFormat(m_format), size);
		tcu::TextureLevel			result				(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), renderWidth, renderHeight);

		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);

		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
		{
			const tcu::CubeFace		face		= tcu::CubeFace(faceNdx);
			const int				stencilVal	= 42*faceNdx;

			texData.allocLevel(face, 0);
			tcu::clearStencil(texData.getLevelFace(0, face), stencilVal);
		}

		glu::TextureTestUtil::computeQuadTexCoordCube(texCoord, tcu::CUBEFACE_NEGATIVE_X, Vec2(-1.5f, -1.3f), Vec2(1.3f, 1.4f));

		gl.bindTexture(GL_TEXTURE_CUBE_MAP, *depthStencilTex);
		gl.texStorage2D(GL_TEXTURE_CUBE_MAP, 1, m_format, size, size);

		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
			glu::texSubImage2D(renderCtx, glu::getGLCubeFace(tcu::CubeFace(faceNdx)), 0, 0, 0, texData.getLevelFace(0, tcu::CubeFace(faceNdx)));

		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, renderWidth, renderHeight);

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
		checkFramebufferStatus(gl);

		blitStencilToColorCube(renderCtx, *depthStencilTex, &texCoord[0], renderWidth, renderHeight);
		glu::readPixels(renderCtx, 0, 0, result);

		{
			using namespace glu::TextureTestUtil;

			tcu::TextureCube		redTex			(TextureFormat(TextureFormat::R, TextureFormat::UNORM_INT8), size);
			const ReferenceParams	sampleParams	(TEXTURETYPE_CUBE, tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE,
																					tcu::Sampler::CLAMP_TO_EDGE,
																					tcu::Sampler::CLAMP_TO_EDGE,
																					tcu::Sampler::NEAREST,
																					tcu::Sampler::NEAREST));
			tcu::LookupPrecision	lookupPrec;
			tcu::LodPrecision		lodPrec;
			bool					compareOk;

			lookupPrec.colorMask		= tcu::BVec4(true, true, true, true);
			lookupPrec.colorThreshold	= tcu::computeFixedPointThreshold(IVec4(8, 8, 8, 8));
			lookupPrec.coordBits		= tcu::IVec3(22, 22, 22);
			lookupPrec.uvwBits			= tcu::IVec3(5, 5, 0);
			lodPrec.lodBits				= 7;
			lodPrec.derivateBits		= 16;

			stencilToUnorm8(texData, redTex);

			compareOk = verifyTextureResult(m_testCtx, result, redTex, &texCoord[0], sampleParams, lookupPrec, lodPrec, tcu::PixelFormat(8, 8, 8, 8));

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

		return STOP;
	}

private:
	const deUint32 m_format;
};

class RenderTex2DCase : public TestCase
{
public:
	RenderTex2DCase (Context& context, const char* name, deUint32 format)
		: TestCase	(context, name, glu::getTextureFormatName(format))
		, m_format	(format)
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
		const glw::Functions&		gl				= renderCtx.getFunctions();
		const int					width			= 117;
		const int					height			= 193;
		glu::Framebuffer			fbo				(renderCtx);
		glu::Renderbuffer			colorBuf		(renderCtx);
		glu::Texture				depthStencilTex	(renderCtx);
		TextureLevel				result			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
		TextureLevel				reference		(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);

		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, *depthStencilTex, 0);
		checkFramebufferStatus(gl);

		drawTestPattern(renderCtx, width, height);

		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
		checkFramebufferStatus(gl);

		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
		glu::readPixels(renderCtx, 0, 0, result.getAccess());

		renderTestPatternReference(reference);

		{
			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), reference, result);
			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
									compareOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}

private:
	const deUint32 m_format;
};

class ClearTex2DCase : public TestCase
{
public:
	ClearTex2DCase (Context& context, const char* name, deUint32 format)
		: TestCase	(context, name, glu::getTextureFormatName(format))
		, m_format	(format)
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
		const glw::Functions&		gl				= renderCtx.getFunctions();
		const int					width			= 125;
		const int					height			= 117;
		const int					cellSize		= 8;
		glu::Framebuffer			fbo				(renderCtx);
		glu::Renderbuffer			colorBuf		(renderCtx);
		glu::Texture				depthStencilTex	(renderCtx);
		TextureLevel				result			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
		TextureLevel				reference		(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);

		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
		gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, *depthStencilTex, 0);
		checkFramebufferStatus(gl);

		gl.enable(GL_SCISSOR_TEST);

		for (int y = 0; y < height; y += cellSize)
		{
			for (int x = 0; x < width; x += cellSize)
			{
				const int		clearW		= de::min(cellSize, width-x);
				const int		clearH		= de::min(cellSize, height-y);
				const int		stencil		= int((deInt32Hash(x) ^ deInt32Hash(y)) & 0xff);

				gl.clearStencil(stencil);
				gl.scissor(x, y, clearW, clearH);
				gl.clear(GL_STENCIL_BUFFER_BIT);

				tcu::clearStencil(tcu::getSubregion(reference.getAccess(), x, y, clearW, clearH), stencil);
			}
		}

		gl.disable(GL_SCISSOR_TEST);

		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
		checkFramebufferStatus(gl);

		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
		glu::readPixels(renderCtx, 0, 0, result.getAccess());

		{
			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), reference, result);
			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
									compareOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}

private:
	const deUint32 m_format;
};

class CompareModeCase : public TestCase
{
public:
	CompareModeCase (Context& context, const char* name, deUint32 format)
		: TestCase	(context, name, glu::getTextureFormatName(format))
		, m_format	(format)
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
		const glw::Functions&		gl					= renderCtx.getFunctions();
		const int					width				= 64;
		const int					height				= 64;
		glu::Framebuffer			fbo					(renderCtx);
		glu::Renderbuffer			colorBuf			(renderCtx);
		glu::Texture				depthStencilTex		(renderCtx);
		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), width, height);
		TextureLevel				readLevel			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
		TextureLevel				stencilOnlyLevel	(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);

		m_testCtx.getLog() << TestLog::Message << "NOTE: Texture compare mode has no effect when reading stencil values." << TestLog::EndMessage;

		renderTestPatternReference(uploadLevel);
		renderTestPatternReference(stencilOnlyLevel);

		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
		gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
		glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, 0, 0, 0, uploadLevel);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
		checkFramebufferStatus(gl);

		blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
		glu::readPixels(renderCtx, 0, 0, readLevel);

		{
			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
									compareOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}

private:
	const deUint32 m_format;
};

class BaseLevelCase : public TestCase
{
public:
	BaseLevelCase (Context& context, const char* name, deUint32 format)
		: TestCase	(context, name, glu::getTextureFormatName(format))
		, m_format	(format)
	{
	}

	IterateResult iterate (void)
	{
		const glu::RenderContext&	renderCtx			= m_context.getRenderContext();
		const glw::Functions&		gl					= renderCtx.getFunctions();
		const int					width				= 128;
		const int					height				= 128;
		const int					levelNdx			= 2;
		const int					levelWidth			= width>>levelNdx;
		const int					levelHeight			= height>>levelNdx;
		glu::Framebuffer			fbo					(renderCtx);
		glu::Renderbuffer			colorBuf			(renderCtx);
		glu::Texture				depthStencilTex		(renderCtx);
		TextureLevel				uploadLevel			(glu::mapGLInternalFormat(m_format), levelWidth, levelHeight);
		TextureLevel				readLevel			(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), levelWidth, levelHeight);
		TextureLevel				stencilOnlyLevel	(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), levelWidth, levelHeight);

		checkDepthStencilFormatSupport(m_context.getContextInfo(), m_format);

		m_testCtx.getLog() << TestLog::Message << "GL_TEXTURE_BASE_LEVEL = " << levelNdx << TestLog::EndMessage;

		renderTestPatternReference(uploadLevel);
		renderTestPatternReference(stencilOnlyLevel);

		gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
		gl.texStorage2D(GL_TEXTURE_2D, deLog2Floor32(de::max(width, height))+1, m_format, width, height);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, levelNdx);
		glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, levelNdx, 0, 0, uploadLevel);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

		gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, levelWidth, levelHeight);

		gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
		checkFramebufferStatus(gl);

		blitStencilToColor2D(renderCtx, *depthStencilTex, levelWidth, levelHeight);
		glu::readPixels(renderCtx, 0, 0, readLevel);

		{
			const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
			m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
									compareOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}

private:
	const deUint32 m_format;
};

} // anonymous

StencilTexturingTests::StencilTexturingTests (Context& context)
	: TestCaseGroup(context, "stencil_texturing", "Stencil texturing tests")
{
}

StencilTexturingTests::~StencilTexturingTests (void)
{
}

void StencilTexturingTests::init (void)
{
	// .format
	{
		tcu::TestCaseGroup* const formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "Formats");
		addChild(formatGroup);

		formatGroup->addChild(new UploadTex2DCase		(m_context, "depth32f_stencil8_2d",			GL_DEPTH32F_STENCIL8));
		formatGroup->addChild(new UploadTex2DArrayCase	(m_context, "depth32f_stencil8_2d_array",	GL_DEPTH32F_STENCIL8));
		formatGroup->addChild(new UploadTexCubeCase		(m_context, "depth32f_stencil8_cube",		GL_DEPTH32F_STENCIL8));
		formatGroup->addChild(new UploadTex2DCase		(m_context, "depth24_stencil8_2d",			GL_DEPTH24_STENCIL8));
		formatGroup->addChild(new UploadTex2DArrayCase	(m_context, "depth24_stencil8_2d_array",	GL_DEPTH24_STENCIL8));
		formatGroup->addChild(new UploadTexCubeCase		(m_context, "depth24_stencil8_cube",		GL_DEPTH24_STENCIL8));

		// OES_texture_stencil8
		formatGroup->addChild(new UploadTex2DCase		(m_context, "stencil_index8_2d",			GL_STENCIL_INDEX8));
		formatGroup->addChild(new UploadTex2DArrayCase	(m_context, "stencil_index8_2d_array",		GL_STENCIL_INDEX8));
		formatGroup->addChild(new UploadTexCubeCase		(m_context, "stencil_index8_cube",			GL_STENCIL_INDEX8));
	}

	// .render
	{
		tcu::TestCaseGroup* const readRenderGroup = new tcu::TestCaseGroup(m_testCtx, "render", "Read rendered stencil values");
		addChild(readRenderGroup);

		readRenderGroup->addChild(new ClearTex2DCase	(m_context, "depth32f_stencil8_clear",	GL_DEPTH32F_STENCIL8));
		readRenderGroup->addChild(new RenderTex2DCase	(m_context, "depth32f_stencil8_draw",	GL_DEPTH32F_STENCIL8));
		readRenderGroup->addChild(new ClearTex2DCase	(m_context, "depth24_stencil8_clear",	GL_DEPTH24_STENCIL8));
		readRenderGroup->addChild(new RenderTex2DCase	(m_context, "depth24_stencil8_draw",	GL_DEPTH24_STENCIL8));
	}

	// .misc
	{
		tcu::TestCaseGroup* const miscGroup = new tcu::TestCaseGroup(m_testCtx, "misc", "Misc cases");
		addChild(miscGroup);

		miscGroup->addChild(new CompareModeCase	(m_context, "compare_mode_effect",	GL_DEPTH24_STENCIL8));
		miscGroup->addChild(new BaseLevelCase	(m_context, "base_level",			GL_DEPTH24_STENCIL8));
	}
}

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