C++程序  |  3432行  |  137 KB

/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) 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 Texture test utilities.
 *//*--------------------------------------------------------------------*/

#include "glsTextureTestUtil.hpp"
#include "gluDefs.hpp"
#include "gluDrawUtil.hpp"
#include "gluRenderContext.hpp"
#include "deRandom.hpp"
#include "tcuTestLog.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuTexLookupVerifier.hpp"
#include "tcuTexCompareVerifier.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "qpWatchDog.h"
#include "deStringUtil.hpp"

using tcu::TestLog;
using std::vector;
using std::string;
using std::map;

namespace deqp
{
namespace gls
{
namespace TextureTestUtil
{

enum
{
	MIN_SUBPIXEL_BITS	= 4
};

SamplerType getSamplerType (tcu::TextureFormat format)
{
	using tcu::TextureFormat;

	switch (format.type)
	{
		case TextureFormat::SIGNED_INT8:
		case TextureFormat::SIGNED_INT16:
		case TextureFormat::SIGNED_INT32:
			return SAMPLERTYPE_INT;

		case TextureFormat::UNSIGNED_INT8:
		case TextureFormat::UNSIGNED_INT32:
		case TextureFormat::UNSIGNED_INT_1010102_REV:
			return SAMPLERTYPE_UINT;

		// Texture formats used in depth/stencil textures.
		case TextureFormat::UNSIGNED_INT16:
		case TextureFormat::UNSIGNED_INT_24_8:
			return (format.order == TextureFormat::D || format.order == TextureFormat::DS) ? SAMPLERTYPE_FLOAT : SAMPLERTYPE_UINT;

		default:
			return SAMPLERTYPE_FLOAT;
	}
}

SamplerType getFetchSamplerType (tcu::TextureFormat format)
{
	using tcu::TextureFormat;

	switch (format.type)
	{
		case TextureFormat::SIGNED_INT8:
		case TextureFormat::SIGNED_INT16:
		case TextureFormat::SIGNED_INT32:
			return SAMPLERTYPE_FETCH_INT;

		case TextureFormat::UNSIGNED_INT8:
		case TextureFormat::UNSIGNED_INT32:
		case TextureFormat::UNSIGNED_INT_1010102_REV:
			return SAMPLERTYPE_FETCH_UINT;

		// Texture formats used in depth/stencil textures.
		case TextureFormat::UNSIGNED_INT16:
		case TextureFormat::UNSIGNED_INT_24_8:
			return (format.order == TextureFormat::D || format.order == TextureFormat::DS) ? SAMPLERTYPE_FETCH_FLOAT : SAMPLERTYPE_FETCH_UINT;

		default:
			return SAMPLERTYPE_FETCH_FLOAT;
	}
}

static tcu::Texture1DView getSubView (const tcu::Texture1DView& view, int baseLevel, int maxLevel)
{
	const int	clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
	const int	clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
	const int	numLevels	= clampedMax-clampedBase+1;
	return tcu::Texture1DView(numLevels, view.getLevels()+clampedBase);
}

static tcu::Texture2DView getSubView (const tcu::Texture2DView& view, int baseLevel, int maxLevel)
{
	const int	clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
	const int	clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
	const int	numLevels	= clampedMax-clampedBase+1;
	return tcu::Texture2DView(numLevels, view.getLevels()+clampedBase);
}

static tcu::TextureCubeView getSubView (const tcu::TextureCubeView& view, int baseLevel, int maxLevel)
{
	const int							clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
	const int							clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
	const int							numLevels	= clampedMax-clampedBase+1;
	const tcu::ConstPixelBufferAccess*	levels[tcu::CUBEFACE_LAST];

	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
		levels[face] = view.getFaceLevels((tcu::CubeFace)face) + clampedBase;

	return tcu::TextureCubeView(numLevels, levels);
}

static tcu::Texture3DView getSubView (const tcu::Texture3DView& view, int baseLevel, int maxLevel)
{
	const int	clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
	const int	clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
	const int	numLevels	= clampedMax-clampedBase+1;
	return tcu::Texture3DView(numLevels, view.getLevels()+clampedBase);
}

static tcu::TextureCubeArrayView getSubView (const tcu::TextureCubeArrayView& view, int baseLevel, int maxLevel)
{
	const int	clampedBase	= de::clamp(baseLevel, 0, view.getNumLevels()-1);
	const int	clampedMax	= de::clamp(maxLevel, clampedBase, view.getNumLevels()-1);
	const int	numLevels	= clampedMax-clampedBase+1;
	return tcu::TextureCubeArrayView(numLevels, view.getLevels()+clampedBase);
}

inline float linearInterpolate (float t, float minVal, float maxVal)
{
	return minVal + (maxVal - minVal) * t;
}

inline tcu::Vec4 linearInterpolate (float t, const tcu::Vec4& a, const tcu::Vec4& b)
{
	return a + (b - a) * t;
}

inline float bilinearInterpolate (float x, float y, const tcu::Vec4& quad)
{
	float w00 = (1.0f-x)*(1.0f-y);
	float w01 = (1.0f-x)*y;
	float w10 = x*(1.0f-y);
	float w11 = x*y;
	return quad.x()*w00 + quad.y()*w10 + quad.z()*w01 + quad.w()*w11;
}

inline float triangleInterpolate (float v0, float v1, float v2, float x, float y)
{
	return v0 + (v2-v0)*x + (v1-v0)*y;
}

inline float triangleInterpolate (const tcu::Vec3& v, float x, float y)
{
	return triangleInterpolate(v.x(), v.y(), v.z(), x, y);
}

inline float triQuadInterpolate (float x, float y, const tcu::Vec4& quad)
{
	// \note Top left fill rule.
	if (x + y < 1.0f)
		return triangleInterpolate(quad.x(), quad.y(), quad.z(), x, y);
	else
		return triangleInterpolate(quad.w(), quad.z(), quad.y(), 1.0f-x, 1.0f-y);
}

SurfaceAccess::SurfaceAccess (tcu::Surface& surface, const tcu::PixelFormat& colorFmt, int x, int y, int width, int height)
	: m_surface		(&surface)
	, m_colorMask	(getColorMask(colorFmt))
	, m_x			(x)
	, m_y			(y)
	, m_width		(width)
	, m_height		(height)
{
}

SurfaceAccess::SurfaceAccess (tcu::Surface& surface, const tcu::PixelFormat& colorFmt)
	: m_surface		(&surface)
	, m_colorMask	(getColorMask(colorFmt))
	, m_x			(0)
	, m_y			(0)
	, m_width		(surface.getWidth())
	, m_height		(surface.getHeight())
{
}

SurfaceAccess::SurfaceAccess (const SurfaceAccess& parent, int x, int y, int width, int height)
	: m_surface			(parent.m_surface)
	, m_colorMask		(parent.m_colorMask)
	, m_x				(parent.m_x + x)
	, m_y				(parent.m_y + y)
	, m_width			(width)
	, m_height			(height)
{
}

// 1D lookup LOD computation.

inline float computeLodFromDerivates (LodMode mode, float dudx, float dudy)
{
	float p = 0.0f;
	switch (mode)
	{
		// \note [mika] Min and max bounds equal to exact with 1D textures
		case LODMODE_EXACT:
		case LODMODE_MIN_BOUND:
		case LODMODE_MAX_BOUND:
			p = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
			break;

		default:
			DE_ASSERT(DE_FALSE);
	}

	return deFloatLog2(p);
}

static float computeNonProjectedTriLod (LodMode mode, const tcu::IVec2& dstSize, deInt32 srcSize, const tcu::Vec3& sq)
{
	float dux	= (sq.z() - sq.x()) * (float)srcSize;
	float duy	= (sq.y() - sq.x()) * (float)srcSize;
	float dx	= (float)dstSize.x();
	float dy	= (float)dstSize.y();

	return computeLodFromDerivates(mode, dux/dx, duy/dy);
}

// 2D lookup LOD computation.

inline float computeLodFromDerivates (LodMode mode, float dudx, float dvdx, float dudy, float dvdy)
{
	float p = 0.0f;
	switch (mode)
	{
		case LODMODE_EXACT:
			p = de::max(deFloatSqrt(dudx*dudx + dvdx*dvdx), deFloatSqrt(dudy*dudy + dvdy*dvdy));
			break;

		case LODMODE_MIN_BOUND:
		case LODMODE_MAX_BOUND:
		{
			float mu = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
			float mv = de::max(deFloatAbs(dvdx), deFloatAbs(dvdy));

			p = mode == LODMODE_MIN_BOUND ? de::max(mu, mv) : mu + mv;
			break;
		}

		default:
			DE_ASSERT(DE_FALSE);
	}

	return deFloatLog2(p);
}

static float computeNonProjectedTriLod (LodMode mode, const tcu::IVec2& dstSize, const tcu::IVec2& srcSize, const tcu::Vec3& sq, const tcu::Vec3& tq)
{
	float dux	= (sq.z() - sq.x()) * (float)srcSize.x();
	float duy	= (sq.y() - sq.x()) * (float)srcSize.x();
	float dvx	= (tq.z() - tq.x()) * (float)srcSize.y();
	float dvy	= (tq.y() - tq.x()) * (float)srcSize.y();
	float dx	= (float)dstSize.x();
	float dy	= (float)dstSize.y();

	return computeLodFromDerivates(mode, dux/dx, dvx/dx, duy/dy, dvy/dy);
}

// 3D lookup LOD computation.

inline float computeLodFromDerivates (LodMode mode, float dudx, float dvdx, float dwdx, float dudy, float dvdy, float dwdy)
{
	float p = 0.0f;
	switch (mode)
	{
		case LODMODE_EXACT:
			p = de::max(deFloatSqrt(dudx*dudx + dvdx*dvdx + dwdx*dwdx), deFloatSqrt(dudy*dudy + dvdy*dvdy + dwdy*dwdy));
			break;

		case LODMODE_MIN_BOUND:
		case LODMODE_MAX_BOUND:
		{
			float mu = de::max(deFloatAbs(dudx), deFloatAbs(dudy));
			float mv = de::max(deFloatAbs(dvdx), deFloatAbs(dvdy));
			float mw = de::max(deFloatAbs(dwdx), deFloatAbs(dwdy));

			p = mode == LODMODE_MIN_BOUND ? de::max(de::max(mu, mv), mw) : (mu + mv + mw);
			break;
		}

		default:
			DE_ASSERT(DE_FALSE);
	}

	return deFloatLog2(p);
}

static float computeNonProjectedTriLod (LodMode mode, const tcu::IVec2& dstSize, const tcu::IVec3& srcSize, const tcu::Vec3& sq, const tcu::Vec3& tq, const tcu::Vec3& rq)
{
	float dux	= (sq.z() - sq.x()) * (float)srcSize.x();
	float duy	= (sq.y() - sq.x()) * (float)srcSize.x();
	float dvx	= (tq.z() - tq.x()) * (float)srcSize.y();
	float dvy	= (tq.y() - tq.x()) * (float)srcSize.y();
	float dwx	= (rq.z() - rq.x()) * (float)srcSize.z();
	float dwy	= (rq.y() - rq.x()) * (float)srcSize.z();
	float dx	= (float)dstSize.x();
	float dy	= (float)dstSize.y();

	return computeLodFromDerivates(mode, dux/dx, dvx/dx, dwx/dx, duy/dy, dvy/dy, dwy/dy);
}

static inline float projectedTriInterpolate (const tcu::Vec3& s, const tcu::Vec3& w, float nx, float ny)
{
	return (s[0]*(1.0f-nx-ny)/w[0] + s[1]*ny/w[1] + s[2]*nx/w[2]) / ((1.0f-nx-ny)/w[0] + ny/w[1] + nx/w[2]);
}

static inline float triDerivateX (const tcu::Vec3& s, const tcu::Vec3& w, float wx, float width, float ny)
{
	float d = w[1]*w[2]*(width*(ny - 1.0f) + wx) - w[0]*(w[2]*width*ny + w[1]*wx);
	return (w[0]*w[1]*w[2]*width * (w[1]*(s[0] - s[2])*(ny - 1.0f) + ny*(w[2]*(s[1] - s[0]) + w[0]*(s[2] - s[1])))) / (d*d);
}

static inline float triDerivateY (const tcu::Vec3& s, const tcu::Vec3& w, float wy, float height, float nx)
{
	float d = w[1]*w[2]*(height*(nx - 1.0f) + wy) - w[0]*(w[1]*height*nx + w[2]*wy);
	return (w[0]*w[1]*w[2]*height * (w[2]*(s[0] - s[1])*(nx - 1.0f) + nx*(w[0]*(s[1] - s[2]) + w[1]*(s[2] - s[0])))) / (d*d);
}

// 1D lookup LOD.
static float computeProjectedTriLod (LodMode mode, const tcu::Vec3& u, const tcu::Vec3& projection, float wx, float wy, float width, float height)
{
	// Exact derivatives.
	float dudx	= triDerivateX(u, projection, wx, width, wy/height);
	float dudy	= triDerivateY(u, projection, wy, height, wx/width);

	return computeLodFromDerivates(mode, dudx, dudy);
}

// 2D lookup LOD.
static float computeProjectedTriLod (LodMode mode, const tcu::Vec3& u, const tcu::Vec3& v, const tcu::Vec3& projection, float wx, float wy, float width, float height)
{
	// Exact derivatives.
	float dudx	= triDerivateX(u, projection, wx, width, wy/height);
	float dvdx	= triDerivateX(v, projection, wx, width, wy/height);
	float dudy	= triDerivateY(u, projection, wy, height, wx/width);
	float dvdy	= triDerivateY(v, projection, wy, height, wx/width);

	return computeLodFromDerivates(mode, dudx, dvdx, dudy, dvdy);
}

// 3D lookup LOD.
static float computeProjectedTriLod (LodMode mode, const tcu::Vec3& u, const tcu::Vec3& v, const tcu::Vec3& w, const tcu::Vec3& projection, float wx, float wy, float width, float height)
{
	// Exact derivatives.
	float dudx	= triDerivateX(u, projection, wx, width, wy/height);
	float dvdx	= triDerivateX(v, projection, wx, width, wy/height);
	float dwdx	= triDerivateX(w, projection, wx, width, wy/height);
	float dudy	= triDerivateY(u, projection, wy, height, wx/width);
	float dvdy	= triDerivateY(v, projection, wy, height, wx/width);
	float dwdy	= triDerivateY(w, projection, wy, height, wx/width);

	return computeLodFromDerivates(mode, dudx, dvdx, dwdx, dudy, dvdy, dwdy);
}

static inline tcu::Vec4 execSample (const tcu::Texture1DView& src, const ReferenceParams& params, float s, float lod)
{
	if (params.samplerType == SAMPLERTYPE_SHADOW)
		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, lod), 0.0, 0.0, 1.0f);
	else
		return src.sample(params.sampler, s, lod);
}

static inline tcu::Vec4 execSample (const tcu::Texture2DView& src, const ReferenceParams& params, float s, float t, float lod)
{
	if (params.samplerType == SAMPLERTYPE_SHADOW)
		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, lod), 0.0, 0.0, 1.0f);
	else
		return src.sample(params.sampler, s, t, lod);
}

static inline tcu::Vec4 execSample (const tcu::TextureCubeView& src, const ReferenceParams& params, float s, float t, float r, float lod)
{
	if (params.samplerType == SAMPLERTYPE_SHADOW)
		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, r, lod), 0.0, 0.0, 1.0f);
	else
		return src.sample(params.sampler, s, t, r, lod);
}

static inline tcu::Vec4 execSample (const tcu::Texture2DArrayView& src, const ReferenceParams& params, float s, float t, float r, float lod)
{
	if (params.samplerType == SAMPLERTYPE_SHADOW)
		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, r, lod), 0.0, 0.0, 1.0f);
	else
		return src.sample(params.sampler, s, t, r, lod);
}

static inline tcu::Vec4 execSample (const tcu::TextureCubeArrayView& src, const ReferenceParams& params, float s, float t, float r, float q, float lod)
{
	if (params.samplerType == SAMPLERTYPE_SHADOW)
		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, r, q, lod), 0.0, 0.0, 1.0f);
	else
		return src.sample(params.sampler, s, t, r, q, lod);
}

static inline tcu::Vec4 execSample (const tcu::Texture1DArrayView& src, const ReferenceParams& params, float s, float t, float lod)
{
	if (params.samplerType == SAMPLERTYPE_SHADOW)
		return tcu::Vec4(src.sampleCompare(params.sampler, params.ref, s, t, lod), 0.0, 0.0, 1.0f);
	else
		return src.sample(params.sampler, s, t, lod);
}

static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture1DView& src, const tcu::Vec4& sq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;

	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
	int			srcSize		= src.getWidth();

	// Coordinates and lod per triangle.
	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	float		triLod[2]	= { de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0]) + lodBias, params.minLod, params.maxLod),
								de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1]) + lodBias, params.minLod, params.maxLod) };

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();

			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
			float	triX	= triNdx ? 1.0f-xf : xf;
			float	triY	= triNdx ? 1.0f-yf : yf;

			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
			float	lod		= triLod[triNdx];

			dst.setPixel(execSample(src, params, s, lod) * params.colorScale + params.colorBias, x, y);
		}
	}
}

static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture2DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;

	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
	tcu::IVec2	srcSize		= tcu::IVec2(src.getWidth(), src.getHeight());

	// Coordinates and lod per triangle.
	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	float		triLod[2]	= { de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0], triT[0]) + lodBias, params.minLod, params.maxLod),
								de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1], triT[1]) + lodBias, params.minLod, params.maxLod) };

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();

			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
			float	triX	= triNdx ? 1.0f-xf : xf;
			float	triY	= triNdx ? 1.0f-yf : yf;

			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
			float	lod		= triLod[triNdx];

			dst.setPixel(execSample(src, params, s, t, lod) * params.colorScale + params.colorBias, x, y);
		}
	}
}

static void sampleTextureProjected (const SurfaceAccess& dst, const tcu::Texture1DView& src, const tcu::Vec4& sq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
	float		dstW		= (float)dst.getWidth();
	float		dstH		= (float)dst.getHeight();

	tcu::Vec4	uq			= sq * (float)src.getWidth();

	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3	triU[2]		= { uq.swizzle(0, 1, 2), uq.swizzle(3, 2, 1) };
	tcu::Vec3	triW[2]		= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };

	for (int py = 0; py < dst.getHeight(); py++)
	{
		for (int px = 0; px < dst.getWidth(); px++)
		{
			float	wx		= (float)px + 0.5f;
			float	wy		= (float)py + 0.5f;
			float	nx		= wx / dstW;
			float	ny		= wy / dstH;

			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
			float	triWx	= triNdx ? dstW - wx : wx;
			float	triWy	= triNdx ? dstH - wy : wy;
			float	triNx	= triNdx ? 1.0f - nx : nx;
			float	triNy	= triNdx ? 1.0f - ny : ny;

			float	s		= projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy);
			float	lod		= computeProjectedTriLod(params.lodMode, triU[triNdx], triW[triNdx], triWx, triWy, (float)dst.getWidth(), (float)dst.getHeight())
							+ lodBias;

			dst.setPixel(execSample(src, params, s, lod) * params.colorScale + params.colorBias, px, py);
		}
	}
}

static void sampleTextureProjected (const SurfaceAccess& dst, const tcu::Texture2DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
	float		dstW		= (float)dst.getWidth();
	float		dstH		= (float)dst.getHeight();

	tcu::Vec4	uq			= sq * (float)src.getWidth();
	tcu::Vec4	vq			= tq * (float)src.getHeight();

	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	tcu::Vec3	triU[2]		= { uq.swizzle(0, 1, 2), uq.swizzle(3, 2, 1) };
	tcu::Vec3	triV[2]		= { vq.swizzle(0, 1, 2), vq.swizzle(3, 2, 1) };
	tcu::Vec3	triW[2]		= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };

	for (int py = 0; py < dst.getHeight(); py++)
	{
		for (int px = 0; px < dst.getWidth(); px++)
		{
			float	wx		= (float)px + 0.5f;
			float	wy		= (float)py + 0.5f;
			float	nx		= wx / dstW;
			float	ny		= wy / dstH;

			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
			float	triWx	= triNdx ? dstW - wx : wx;
			float	triWy	= triNdx ? dstH - wy : wy;
			float	triNx	= triNdx ? 1.0f - nx : nx;
			float	triNy	= triNdx ? 1.0f - ny : ny;

			float	s		= projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy);
			float	t		= projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy);
			float	lod		= computeProjectedTriLod(params.lodMode, triU[triNdx], triV[triNdx], triW[triNdx], triWx, triWy, (float)dst.getWidth(), (float)dst.getHeight())
							+ lodBias;

			dst.setPixel(execSample(src, params, s, t, lod) * params.colorScale + params.colorBias, px, py);
		}
	}
}

void sampleTexture (const SurfaceAccess& dst, const tcu::Texture2DView& src, const float* texCoord, const ReferenceParams& params)
{
	const tcu::Texture2DView	view	= getSubView(src, params.baseLevel, params.maxLevel);
	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
	const tcu::Vec4				tq		= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);

	if (params.flags & ReferenceParams::PROJECTED)
		sampleTextureProjected(dst, view, sq, tq, params);
	else
		sampleTextureNonProjected(dst, view, sq, tq, params);
}

void sampleTexture (const SurfaceAccess& dst, const tcu::Texture1DView& src, const float* texCoord, const ReferenceParams& params)
{
	const tcu::Texture1DView	view	= getSubView(src, params.baseLevel, params.maxLevel);
	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0], texCoord[1], texCoord[2], texCoord[3]);

	if (params.flags & ReferenceParams::PROJECTED)
		sampleTextureProjected(dst, view, sq, params);
	else
		sampleTextureNonProjected(dst, view, sq, params);
}

static float computeCubeLodFromDerivates (LodMode lodMode, const tcu::Vec3& coord, const tcu::Vec3& coordDx, const tcu::Vec3& coordDy, const int faceSize)
{
	const tcu::CubeFace	face	= tcu::selectCubeFace(coord);
	int					maNdx	= 0;
	int					sNdx	= 0;
	int					tNdx	= 0;

	// \note Derivate signs don't matter when computing lod
	switch (face)
	{
		case tcu::CUBEFACE_NEGATIVE_X:
		case tcu::CUBEFACE_POSITIVE_X: maNdx = 0; sNdx = 2; tNdx = 1; break;
		case tcu::CUBEFACE_NEGATIVE_Y:
		case tcu::CUBEFACE_POSITIVE_Y: maNdx = 1; sNdx = 0; tNdx = 2; break;
		case tcu::CUBEFACE_NEGATIVE_Z:
		case tcu::CUBEFACE_POSITIVE_Z: maNdx = 2; sNdx = 0; tNdx = 1; break;
		default:
			DE_ASSERT(DE_FALSE);
	}

	{
		const float		sc		= coord[sNdx];
		const float		tc		= coord[tNdx];
		const float		ma		= de::abs(coord[maNdx]);
		const float		scdx	= coordDx[sNdx];
		const float		tcdx	= coordDx[tNdx];
		const float		madx	= de::abs(coordDx[maNdx]);
		const float		scdy	= coordDy[sNdx];
		const float		tcdy	= coordDy[tNdx];
		const float		mady	= de::abs(coordDy[maNdx]);
		const float		dudx	= float(faceSize) * 0.5f * (scdx*ma - sc*madx) / (ma*ma);
		const float		dvdx	= float(faceSize) * 0.5f * (tcdx*ma - tc*madx) / (ma*ma);
		const float		dudy	= float(faceSize) * 0.5f * (scdy*ma - sc*mady) / (ma*ma);
		const float		dvdy	= float(faceSize) * 0.5f * (tcdy*ma - tc*mady) / (ma*ma);

		return computeLodFromDerivates(lodMode, dudx, dvdx, dudy, dvdy);
	}
}

static void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
{
	const tcu::IVec2	dstSize			= tcu::IVec2(dst.getWidth(), dst.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const int			srcSize			= src.getSize();

	// Coordinates per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };

	const float			lodBias			((params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f);

	for (int py = 0; py < dst.getHeight(); py++)
	{
		for (int px = 0; px < dst.getWidth(); px++)
		{
			const float		wx		= (float)px + 0.5f;
			const float		wy		= (float)py + 0.5f;
			const float		nx		= wx / dstW;
			const float		ny		= wy / dstH;

			const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
			const float		triNx	= triNdx ? 1.0f - nx : nx;
			const float		triNy	= triNdx ? 1.0f - ny : ny;

			const tcu::Vec3	coord		(triangleInterpolate(triS[triNdx], triNx, triNy),
										 triangleInterpolate(triT[triNdx], triNx, triNy),
										 triangleInterpolate(triR[triNdx], triNx, triNy));
			const tcu::Vec3	coordDx		(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
										 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
										 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
			const tcu::Vec3	coordDy		(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
										 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
										 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));

			const float		lod			= de::clamp(computeCubeLodFromDerivates(params.lodMode, coord, coordDx, coordDy, srcSize) + lodBias, params.minLod, params.maxLod);

			dst.setPixel(execSample(src, params, coord.x(), coord.y(), coord.z(), lod) * params.colorScale + params.colorBias, px, py);
		}
	}
}

void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeView& src, const float* texCoord, const ReferenceParams& params)
{
	const tcu::TextureCubeView	view	= getSubView(src, params.baseLevel, params.maxLevel);
	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	const tcu::Vec4				tq		= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	const tcu::Vec4				rq		= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	return sampleTexture(dst, view, sq, tq, rq, params);
}

// \todo [2013-07-17 pyry] Remove this!
void sampleTextureMultiFace (const SurfaceAccess& dst, const tcu::TextureCubeView& src, const float* texCoord, const ReferenceParams& params)
{
	return sampleTexture(dst, src, texCoord, params);
}

static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture2DArrayView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;

	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
	tcu::IVec2	srcSize		= tcu::IVec2(src.getWidth(), src.getHeight());

	// Coordinates and lod per triangle.
	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	tcu::Vec3	triR[2]		= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	float		triLod[2]	= { computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0], triT[0]) + lodBias,
								computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1], triT[1]) + lodBias};

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();

			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
			float	triX	= triNdx ? 1.0f-xf : xf;
			float	triY	= triNdx ? 1.0f-yf : yf;

			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
			float	r		= triangleInterpolate(triR[triNdx].x(), triR[triNdx].y(), triR[triNdx].z(), triX, triY);
			float	lod		= triLod[triNdx];

			dst.setPixel(execSample(src, params, s, t, r, lod) * params.colorScale + params.colorBias, x, y);
		}
	}
}

void sampleTexture (const SurfaceAccess& dst, const tcu::Texture2DArrayView& src, const float* texCoord, const ReferenceParams& params)
{
	tcu::Vec4 sq = tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	tcu::Vec4 tq = tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	tcu::Vec4 rq = tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	DE_ASSERT(!(params.flags & ReferenceParams::PROJECTED)); // \todo [2012-02-17 pyry] Support projected lookups.
	sampleTextureNonProjected(dst, src, sq, tq, rq, params);
}

static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture1DArrayView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;

	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
	deInt32		srcSize		= src.getWidth();

	// Coordinates and lod per triangle.
	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	float		triLod[2]	= { computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0]) + lodBias,
								computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1]) + lodBias};

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();

			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
			float	triX	= triNdx ? 1.0f-xf : xf;
			float	triY	= triNdx ? 1.0f-yf : yf;

			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
			float	lod		= triLod[triNdx];

			dst.setPixel(execSample(src, params, s, t, lod) * params.colorScale + params.colorBias, x, y);
		}
	}
}

void sampleTexture (const SurfaceAccess& dst, const tcu::Texture1DArrayView& src, const float* texCoord, const ReferenceParams& params)
{
	tcu::Vec4 sq = tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
	tcu::Vec4 tq = tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);

	DE_ASSERT(!(params.flags & ReferenceParams::PROJECTED)); // \todo [2014-06-09 mika] Support projected lookups.
	sampleTextureNonProjected(dst, src, sq, tq, params);
}

static void sampleTextureNonProjected (const SurfaceAccess& dst, const tcu::Texture3DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;

	tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
	tcu::IVec3	srcSize		= tcu::IVec3(src.getWidth(), src.getHeight(), src.getDepth());

	// Coordinates and lod per triangle.
	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	tcu::Vec3	triR[2]		= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	float		triLod[2]	= { de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[0], triT[0], triR[0]) + lodBias, params.minLod, params.maxLod),
								de::clamp(computeNonProjectedTriLod(params.lodMode, dstSize, srcSize, triS[1], triT[1], triR[1]) + lodBias, params.minLod, params.maxLod) };

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
			float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();

			int		triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
			float	triX	= triNdx ? 1.0f-xf : xf;
			float	triY	= triNdx ? 1.0f-yf : yf;

			float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);
			float	t		= triangleInterpolate(triT[triNdx].x(), triT[triNdx].y(), triT[triNdx].z(), triX, triY);
			float	r		= triangleInterpolate(triR[triNdx].x(), triR[triNdx].y(), triR[triNdx].z(), triX, triY);
			float	lod		= triLod[triNdx];

			dst.setPixel(src.sample(params.sampler, s, t, r, lod) * params.colorScale + params.colorBias, x, y);
		}
	}
}

static void sampleTextureProjected (const SurfaceAccess& dst, const tcu::Texture3DView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const ReferenceParams& params)
{
	float		lodBias		= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;
	float		dstW		= (float)dst.getWidth();
	float		dstH		= (float)dst.getHeight();

	tcu::Vec4	uq			= sq * (float)src.getWidth();
	tcu::Vec4	vq			= tq * (float)src.getHeight();
	tcu::Vec4	wq			= rq * (float)src.getDepth();

	tcu::Vec3	triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3	triT[2]		= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	tcu::Vec3	triR[2]		= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	tcu::Vec3	triU[2]		= { uq.swizzle(0, 1, 2), uq.swizzle(3, 2, 1) };
	tcu::Vec3	triV[2]		= { vq.swizzle(0, 1, 2), vq.swizzle(3, 2, 1) };
	tcu::Vec3	triW[2]		= { wq.swizzle(0, 1, 2), wq.swizzle(3, 2, 1) };
	tcu::Vec3	triP[2]		= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };

	for (int py = 0; py < dst.getHeight(); py++)
	{
		for (int px = 0; px < dst.getWidth(); px++)
		{
			float	wx		= (float)px + 0.5f;
			float	wy		= (float)py + 0.5f;
			float	nx		= wx / dstW;
			float	ny		= wy / dstH;

			int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
			float	triWx	= triNdx ? dstW - wx : wx;
			float	triWy	= triNdx ? dstH - wy : wy;
			float	triNx	= triNdx ? 1.0f - nx : nx;
			float	triNy	= triNdx ? 1.0f - ny : ny;

			float	s		= projectedTriInterpolate(triS[triNdx], triP[triNdx], triNx, triNy);
			float	t		= projectedTriInterpolate(triT[triNdx], triP[triNdx], triNx, triNy);
			float	r		= projectedTriInterpolate(triR[triNdx], triP[triNdx], triNx, triNy);
			float	lod		= computeProjectedTriLod(params.lodMode, triU[triNdx], triV[triNdx], triW[triNdx], triP[triNdx], triWx, triWy, (float)dst.getWidth(), (float)dst.getHeight())
							+ lodBias;

			dst.setPixel(src.sample(params.sampler, s, t, r, lod) * params.colorScale + params.colorBias, px, py);
		}
	}
}

void sampleTexture (const SurfaceAccess& dst, const tcu::Texture3DView& src, const float* texCoord, const ReferenceParams& params)
{
	const tcu::Texture3DView	view	= getSubView(src, params.baseLevel, params.maxLevel);
	const tcu::Vec4				sq		= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	const tcu::Vec4				tq		= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	const tcu::Vec4				rq		= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	if (params.flags & ReferenceParams::PROJECTED)
		sampleTextureProjected(dst, view, sq, tq, rq, params);
	else
		sampleTextureNonProjected(dst, view, sq, tq, rq, params);
}

static void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeArrayView& src, const tcu::Vec4& sq, const tcu::Vec4& tq, const tcu::Vec4& rq, const tcu::Vec4& qq, const ReferenceParams& params)
{
	const float		dstW	= (float)dst.getWidth();
	const float		dstH	= (float)dst.getHeight();

	// Coordinates per triangle.
	tcu::Vec3		triS[2]	= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	tcu::Vec3		triT[2]	= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	tcu::Vec3		triR[2]	= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	tcu::Vec3		triQ[2]	= { qq.swizzle(0, 1, 2), qq.swizzle(3, 2, 1) };
	const tcu::Vec3	triW[2]	= { params.w.swizzle(0, 1, 2), params.w.swizzle(3, 2, 1) };

	const float		lodBias	= (params.flags & ReferenceParams::USE_BIAS) ? params.bias : 0.0f;

	for (int py = 0; py < dst.getHeight(); py++)
	{
		for (int px = 0; px < dst.getWidth(); px++)
		{
			const float		wx		= (float)px + 0.5f;
			const float		wy		= (float)py + 0.5f;
			const float		nx		= wx / dstW;
			const float		ny		= wy / dstH;

			const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
			const float		triNx	= triNdx ? 1.0f - nx : nx;
			const float		triNy	= triNdx ? 1.0f - ny : ny;

			const tcu::Vec3	coord	(triangleInterpolate(triS[triNdx], triNx, triNy),
									 triangleInterpolate(triT[triNdx], triNx, triNy),
									 triangleInterpolate(triR[triNdx], triNx, triNy));

			const float		coordQ	= triangleInterpolate(triQ[triNdx], triNx, triNy);

			const tcu::Vec3	coordDx	(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
									 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
									 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
			const tcu::Vec3	coordDy	(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
									 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
									 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));

			const float		lod		= de::clamp(computeCubeLodFromDerivates(params.lodMode, coord, coordDx, coordDy, src.getSize()) + lodBias, params.minLod, params.maxLod);

			dst.setPixel(execSample(src, params, coord.x(), coord.y(), coord.z(), coordQ, lod) * params.colorScale + params.colorBias, px, py);
		}
	}
}

void sampleTexture (const SurfaceAccess& dst, const tcu::TextureCubeArrayView& src, const float* texCoord, const ReferenceParams& params)
{
	tcu::Vec4 sq = tcu::Vec4(texCoord[0+0], texCoord[4+0], texCoord[8+0], texCoord[12+0]);
	tcu::Vec4 tq = tcu::Vec4(texCoord[0+1], texCoord[4+1], texCoord[8+1], texCoord[12+1]);
	tcu::Vec4 rq = tcu::Vec4(texCoord[0+2], texCoord[4+2], texCoord[8+2], texCoord[12+2]);
	tcu::Vec4 qq = tcu::Vec4(texCoord[0+3], texCoord[4+3], texCoord[8+3], texCoord[12+3]);

	sampleTexture(dst, src, sq, tq, rq, qq, params);
}

void fetchTexture (const SurfaceAccess& dst, const tcu::ConstPixelBufferAccess& src, const float* texCoord, const tcu::Vec4& colorScale, const tcu::Vec4& colorBias)
{
	const tcu::Vec4		sq			= tcu::Vec4(texCoord[0], texCoord[1], texCoord[2], texCoord[3]);
	const tcu::IVec2	dstSize		= tcu::IVec2(dst.getWidth(), dst.getHeight());
	const tcu::Vec3		triS[2]		= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			const float	yf		= ((float)y + 0.5f) / (float)dst.getHeight();
			const float	xf		= ((float)x + 0.5f) / (float)dst.getWidth();

			const int	triNdx	= xf + yf >= 1.0f ? 1 : 0; // Top left fill rule.
			const float	triX	= triNdx ? 1.0f-xf : xf;
			const float	triY	= triNdx ? 1.0f-yf : yf;

			const float	s		= triangleInterpolate(triS[triNdx].x(), triS[triNdx].y(), triS[triNdx].z(), triX, triY);

			dst.setPixel(src.getPixel((int)s, 0) * colorScale + colorBias, x, y);
		}
	}
}

void clear (const SurfaceAccess& dst, const tcu::Vec4& color)
{
	for (int y = 0; y < dst.getHeight(); y++)
		for (int x = 0; x < dst.getWidth(); x++)
			dst.setPixel(color, x, y);
}

bool compareImages (TestLog& log, const tcu::Surface& reference, const tcu::Surface& rendered, tcu::RGBA threshold)
{
	return tcu::pixelThresholdCompare(log, "Result", "Image comparison result", reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
}

bool compareImages (TestLog& log, const char* name, const char* desc, const tcu::Surface& reference, const tcu::Surface& rendered, tcu::RGBA threshold)
{
	return tcu::pixelThresholdCompare(log, name, desc, reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
}

int measureAccuracy (tcu::TestLog& log, const tcu::Surface& reference, const tcu::Surface& rendered, int bestScoreDiff, int worstScoreDiff)
{
	return tcu::measurePixelDiffAccuracy(log, "Result", "Image comparison result", reference, rendered, bestScoreDiff, worstScoreDiff, tcu::COMPARE_LOG_EVERYTHING);
}

inline int rangeDiff (int x, int a, int b)
{
	if (x < a)
		return a-x;
	else if (x > b)
		return x-b;
	else
		return 0;
}

inline tcu::RGBA rangeDiff (tcu::RGBA p, tcu::RGBA a, tcu::RGBA b)
{
	int rMin = de::min(a.getRed(),		b.getRed());
	int rMax = de::max(a.getRed(),		b.getRed());
	int gMin = de::min(a.getGreen(),	b.getGreen());
	int gMax = de::max(a.getGreen(),	b.getGreen());
	int bMin = de::min(a.getBlue(),		b.getBlue());
	int bMax = de::max(a.getBlue(),		b.getBlue());
	int aMin = de::min(a.getAlpha(),	b.getAlpha());
	int aMax = de::max(a.getAlpha(),	b.getAlpha());

	return tcu::RGBA(rangeDiff(p.getRed(),		rMin, rMax),
					 rangeDiff(p.getGreen(),	gMin, gMax),
					 rangeDiff(p.getBlue(),		bMin, bMax),
					 rangeDiff(p.getAlpha(),	aMin, aMax));
}

inline bool rangeCompare (tcu::RGBA p, tcu::RGBA a, tcu::RGBA b, tcu::RGBA threshold)
{
	tcu::RGBA diff = rangeDiff(p, a, b);
	return diff.getRed()	<= threshold.getRed() &&
		   diff.getGreen()	<= threshold.getGreen() &&
		   diff.getBlue()	<= threshold.getBlue() &&
		   diff.getAlpha()	<= threshold.getAlpha();
}

RandomViewport::RandomViewport (const tcu::RenderTarget& renderTarget, int preferredWidth, int preferredHeight, deUint32 seed)
	: x			(0)
	, y			(0)
	, width		(deMin32(preferredWidth, renderTarget.getWidth()))
	, height	(deMin32(preferredHeight, renderTarget.getHeight()))
{
	de::Random rnd(seed);
	x = rnd.getInt(0, renderTarget.getWidth()	- width);
	y = rnd.getInt(0, renderTarget.getHeight()	- height);
}

ProgramLibrary::ProgramLibrary (const glu::RenderContext& context, tcu::TestContext& testCtx, glu::GLSLVersion glslVersion, glu::Precision texCoordPrecision)
	: m_context				(context)
	, m_testCtx				(testCtx)
	, m_glslVersion			(glslVersion)
	, m_texCoordPrecision	(texCoordPrecision)
{
}

ProgramLibrary::~ProgramLibrary (void)
{
	clear();
}

void ProgramLibrary::clear (void)
{
	for (map<Program, glu::ShaderProgram*>::iterator i = m_programs.begin(); i != m_programs.end(); i++)
	{
		delete i->second;
		i->second = DE_NULL;
	}
	m_programs.clear();
}

glu::ShaderProgram* ProgramLibrary::getProgram (Program program)
{
	TestLog& log = m_testCtx.getLog();

	if (m_programs.find(program) != m_programs.end())
		return m_programs[program]; // Return from cache.

	static const char* vertShaderTemplate =
		"${VTX_HEADER}"
		"${VTX_IN} highp vec4 a_position;\n"
		"${VTX_IN} ${PRECISION} ${TEXCOORD_TYPE} a_texCoord;\n"
		"${VTX_OUT} ${PRECISION} ${TEXCOORD_TYPE} v_texCoord;\n"
		"\n"
		"void main (void)\n"
		"{\n"
		"	gl_Position = a_position;\n"
		"	v_texCoord = a_texCoord;\n"
		"}\n";
	static const char* fragShaderTemplate =
		"${FRAG_HEADER}"
		"${FRAG_IN} ${PRECISION} ${TEXCOORD_TYPE} v_texCoord;\n"
		"uniform ${PRECISION} float u_bias;\n"
		"uniform ${PRECISION} float u_ref;\n"
		"uniform ${PRECISION} vec4 u_colorScale;\n"
		"uniform ${PRECISION} vec4 u_colorBias;\n"
		"uniform ${PRECISION} ${SAMPLER_TYPE} u_sampler;\n"
		"\n"
		"void main (void)\n"
		"{\n"
		"	${FRAG_COLOR} = ${LOOKUP} * u_colorScale + u_colorBias;\n"
		"}\n";

	map<string, string> params;

	bool	isCube		= de::inRange<int>(program, PROGRAM_CUBE_FLOAT, PROGRAM_CUBE_SHADOW_BIAS);
	bool	isArray		= de::inRange<int>(program, PROGRAM_2D_ARRAY_FLOAT, PROGRAM_2D_ARRAY_SHADOW)
							|| de::inRange<int>(program, PROGRAM_1D_ARRAY_FLOAT, PROGRAM_1D_ARRAY_SHADOW);

	bool	is1D		= de::inRange<int>(program, PROGRAM_1D_FLOAT, PROGRAM_1D_UINT_BIAS)
							|| de::inRange<int>(program, PROGRAM_1D_ARRAY_FLOAT, PROGRAM_1D_ARRAY_SHADOW)
							|| de::inRange<int>(program, PROGRAM_BUFFER_FLOAT, PROGRAM_BUFFER_UINT);

	bool	is2D		= de::inRange<int>(program, PROGRAM_2D_FLOAT, PROGRAM_2D_UINT_BIAS)
							|| de::inRange<int>(program, PROGRAM_2D_ARRAY_FLOAT, PROGRAM_2D_ARRAY_SHADOW);

	bool	is3D		= de::inRange<int>(program, PROGRAM_3D_FLOAT, PROGRAM_3D_UINT_BIAS);
	bool	isCubeArray	= de::inRange<int>(program, PROGRAM_CUBE_ARRAY_FLOAT, PROGRAM_CUBE_ARRAY_SHADOW);
	bool	isBuffer	= de::inRange<int>(program, PROGRAM_BUFFER_FLOAT, PROGRAM_BUFFER_UINT);

	if (m_glslVersion == glu::GLSL_VERSION_100_ES)
	{
		params["FRAG_HEADER"]	= "";
		params["VTX_HEADER"]	= "";
		params["VTX_IN"]		= "attribute";
		params["VTX_OUT"]		= "varying";
		params["FRAG_IN"]		= "varying";
		params["FRAG_COLOR"]	= "gl_FragColor";
	}
	else if (m_glslVersion == glu::GLSL_VERSION_300_ES || m_glslVersion == glu::GLSL_VERSION_310_ES || m_glslVersion == glu::GLSL_VERSION_330)
	{
		const string	version	= glu::getGLSLVersionDeclaration(m_glslVersion);
		const char*		ext		= DE_NULL;

		if (isCubeArray && glu::glslVersionIsES(m_glslVersion))
			ext = "GL_EXT_texture_cube_map_array";
		else if (isBuffer && glu::glslVersionIsES(m_glslVersion))
			ext = "GL_EXT_texture_buffer";

		params["FRAG_HEADER"]	= version + (ext ? string("\n#extension ") + ext + " : require" : string()) + "\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
		params["VTX_HEADER"]	= version + "\n";
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
	}
	else
		DE_ASSERT(!"Unsupported version");

	params["PRECISION"]		= glu::getPrecisionName(m_texCoordPrecision);

	if (isCubeArray)
		params["TEXCOORD_TYPE"]	= "vec4";
	else if (isCube || (is2D && isArray) || is3D)
		params["TEXCOORD_TYPE"]	= "vec3";
	else if ((is1D && isArray) || is2D)
		params["TEXCOORD_TYPE"]	= "vec2";
	else if (is1D)
		params["TEXCOORD_TYPE"]	= "float";
	else
		DE_ASSERT(DE_FALSE);

	const char*	sampler	= DE_NULL;
	const char*	lookup	= DE_NULL;

	if (m_glslVersion == glu::GLSL_VERSION_300_ES || m_glslVersion == glu::GLSL_VERSION_310_ES || m_glslVersion == glu::GLSL_VERSION_330)
	{
		switch (program)
		{
			case PROGRAM_2D_FLOAT:			sampler = "sampler2D";				lookup = "texture(u_sampler, v_texCoord)";												break;
			case PROGRAM_2D_INT:			sampler = "isampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_2D_UINT:			sampler = "usampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_2D_SHADOW:			sampler = "sampler2DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
			case PROGRAM_2D_FLOAT_BIAS:		sampler = "sampler2D";				lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
			case PROGRAM_2D_INT_BIAS:		sampler = "isampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_2D_UINT_BIAS:		sampler = "usampler2D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_2D_SHADOW_BIAS:	sampler = "sampler2DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref), u_bias), 0.0, 0.0, 1.0)";	break;
			case PROGRAM_1D_FLOAT:			sampler = "sampler1D";				lookup = "texture(u_sampler, v_texCoord)";												break;
			case PROGRAM_1D_INT:			sampler = "isampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_1D_UINT:			sampler = "usampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_1D_SHADOW:			sampler = "sampler1DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
			case PROGRAM_1D_FLOAT_BIAS:		sampler = "sampler1D";				lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
			case PROGRAM_1D_INT_BIAS:		sampler = "isampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_1D_UINT_BIAS:		sampler = "usampler1D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_1D_SHADOW_BIAS:	sampler = "sampler1DShadow";		lookup = "vec4(texture(u_sampler, vec3(v_texCoord, u_ref), u_bias), 0.0, 0.0, 1.0)";	break;
			case PROGRAM_CUBE_FLOAT:		sampler = "samplerCube";			lookup = "texture(u_sampler, v_texCoord)";												break;
			case PROGRAM_CUBE_INT:			sampler = "isamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_CUBE_UINT:			sampler = "usamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_CUBE_SHADOW:		sampler = "samplerCubeShadow";		lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
			case PROGRAM_CUBE_FLOAT_BIAS:	sampler = "samplerCube";			lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
			case PROGRAM_CUBE_INT_BIAS:		sampler = "isamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_CUBE_UINT_BIAS:	sampler = "usamplerCube";			lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_CUBE_SHADOW_BIAS:	sampler = "samplerCubeShadow";		lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref), u_bias), 0.0, 0.0, 1.0)";	break;
			case PROGRAM_2D_ARRAY_FLOAT:	sampler = "sampler2DArray";			lookup = "texture(u_sampler, v_texCoord)";												break;
			case PROGRAM_2D_ARRAY_INT:		sampler = "isampler2DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_2D_ARRAY_UINT:		sampler = "usampler2DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_2D_ARRAY_SHADOW:	sampler = "sampler2DArrayShadow";	lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
			case PROGRAM_3D_FLOAT:			sampler = "sampler3D";				lookup = "texture(u_sampler, v_texCoord)";												break;
			case PROGRAM_3D_INT:			sampler = "isampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_3D_UINT:			sampler =" usampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_3D_FLOAT_BIAS:		sampler = "sampler3D";				lookup = "texture(u_sampler, v_texCoord, u_bias)";										break;
			case PROGRAM_3D_INT_BIAS:		sampler = "isampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_3D_UINT_BIAS:		sampler =" usampler3D";				lookup = "vec4(texture(u_sampler, v_texCoord, u_bias))";								break;
			case PROGRAM_CUBE_ARRAY_FLOAT:	sampler = "samplerCubeArray";		lookup = "texture(u_sampler, v_texCoord)";												break;
			case PROGRAM_CUBE_ARRAY_INT:	sampler = "isamplerCubeArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_CUBE_ARRAY_UINT:	sampler = "usamplerCubeArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_CUBE_ARRAY_SHADOW:	sampler = "samplerCubeArrayShadow";	lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
			case PROGRAM_1D_ARRAY_FLOAT:	sampler = "sampler1DArray";			lookup = "texture(u_sampler, v_texCoord)";												break;
			case PROGRAM_1D_ARRAY_INT:		sampler = "isampler1DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_1D_ARRAY_UINT:		sampler = "usampler1DArray";		lookup = "vec4(texture(u_sampler, v_texCoord))";										break;
			case PROGRAM_1D_ARRAY_SHADOW:	sampler = "sampler1DArrayShadow";	lookup = "vec4(texture(u_sampler, vec4(v_texCoord, u_ref)), 0.0, 0.0, 1.0)";			break;
			case PROGRAM_BUFFER_FLOAT:		sampler = "samplerBuffer";			lookup = "texelFetch(u_sampler, int(v_texCoord))";										break;
			case PROGRAM_BUFFER_INT:		sampler = "isamplerBuffer";			lookup = "vec4(texelFetch(u_sampler, int(v_texCoord)))";								break;
			case PROGRAM_BUFFER_UINT:		sampler = "usamplerBuffer";			lookup = "vec4(texelFetch(u_sampler, int(v_texCoord)))";								break;
			default:
				DE_ASSERT(false);
		}
	}
	else if (m_glslVersion == glu::GLSL_VERSION_100_ES)
	{
		sampler = isCube ? "samplerCube" : "sampler2D";

		switch (program)
		{
			case PROGRAM_2D_FLOAT:			lookup = "texture2D(u_sampler, v_texCoord)";			break;
			case PROGRAM_2D_FLOAT_BIAS:		lookup = "texture2D(u_sampler, v_texCoord, u_bias)";	break;
			case PROGRAM_CUBE_FLOAT:		lookup = "textureCube(u_sampler, v_texCoord)";			break;
			case PROGRAM_CUBE_FLOAT_BIAS:	lookup = "textureCube(u_sampler, v_texCoord, u_bias)";	break;
			default:
				DE_ASSERT(false);
		}
	}
	else
		DE_ASSERT(!"Unsupported version");

	params["SAMPLER_TYPE"]	= sampler;
	params["LOOKUP"]		= lookup;

	std::string vertSrc = tcu::StringTemplate(vertShaderTemplate).specialize(params);
	std::string fragSrc = tcu::StringTemplate(fragShaderTemplate).specialize(params);

	glu::ShaderProgram* progObj = new glu::ShaderProgram(m_context, glu::makeVtxFragSources(vertSrc, fragSrc));
	if (!progObj->isOk())
	{
		log << *progObj;
		delete progObj;
		TCU_FAIL("Failed to compile shader program");
	}

	try
	{
		m_programs[program] = progObj;
	}
	catch (...)
	{
		delete progObj;
		throw;
	}

	return progObj;
}

TextureRenderer::TextureRenderer (const glu::RenderContext& context, tcu::TestContext& testCtx, glu::GLSLVersion glslVersion, glu::Precision texCoordPrecision)
	: m_renderCtx		(context)
	, m_testCtx			(testCtx)
	, m_programLibrary	(context, testCtx, glslVersion, texCoordPrecision)
{
}

TextureRenderer::~TextureRenderer (void)
{
	clear();
}

void TextureRenderer::clear (void)
{
	m_programLibrary.clear();
}

void TextureRenderer::renderQuad (int texUnit, const float* texCoord, TextureType texType)
{
	renderQuad(texUnit, texCoord, RenderParams(texType));
}

void TextureRenderer::renderQuad (int texUnit, const float* texCoord, const RenderParams& params)
{
	const glw::Functions&	gl			= m_renderCtx.getFunctions();
	tcu::Vec4				wCoord		= params.flags & RenderParams::PROJECTED ? params.w : tcu::Vec4(1.0f);
	bool					useBias		= !!(params.flags & RenderParams::USE_BIAS);
	TestLog&				log			= m_testCtx.getLog();
	bool					logUniforms	= !!(params.flags & RenderParams::LOG_UNIFORMS);

	// Render quad with texture.
	float position[] =
	{
		-1.0f*wCoord.x(), -1.0f*wCoord.x(), 0.0f, wCoord.x(),
		-1.0f*wCoord.y(), +1.0f*wCoord.y(), 0.0f, wCoord.y(),
		+1.0f*wCoord.z(), -1.0f*wCoord.z(), 0.0f, wCoord.z(),
		+1.0f*wCoord.w(), +1.0f*wCoord.w(), 0.0f, wCoord.w()
	};
	static const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };

	Program progSpec	= PROGRAM_LAST;
	int		numComps	= 0;
	if (params.texType == TEXTURETYPE_2D)
	{
		numComps = 2;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_2D_FLOAT_BIAS	: PROGRAM_2D_FLOAT;		break;
			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_2D_INT_BIAS	: PROGRAM_2D_INT;		break;
			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_2D_UINT_BIAS	: PROGRAM_2D_UINT;		break;
			case SAMPLERTYPE_SHADOW:	progSpec = useBias ? PROGRAM_2D_SHADOW_BIAS	: PROGRAM_2D_SHADOW;	break;
			default:					DE_ASSERT(false);
		}
	}
	else if (params.texType == TEXTURETYPE_1D)
	{
		numComps = 1;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_1D_FLOAT_BIAS	: PROGRAM_1D_FLOAT;		break;
			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_1D_INT_BIAS	: PROGRAM_1D_INT;		break;
			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_1D_UINT_BIAS	: PROGRAM_1D_UINT;		break;
			case SAMPLERTYPE_SHADOW:	progSpec = useBias ? PROGRAM_1D_SHADOW_BIAS	: PROGRAM_1D_SHADOW;	break;
			default:					DE_ASSERT(false);
		}
	}
	else if (params.texType == TEXTURETYPE_CUBE)
	{
		numComps = 3;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_CUBE_FLOAT_BIAS	: PROGRAM_CUBE_FLOAT;	break;
			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_CUBE_INT_BIAS		: PROGRAM_CUBE_INT;		break;
			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_CUBE_UINT_BIAS		: PROGRAM_CUBE_UINT;	break;
			case SAMPLERTYPE_SHADOW:	progSpec = useBias ? PROGRAM_CUBE_SHADOW_BIAS	: PROGRAM_CUBE_SHADOW;	break;
			default:					DE_ASSERT(false);
		}
	}
	else if (params.texType == TEXTURETYPE_3D)
	{
		numComps = 3;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FLOAT:		progSpec = useBias ? PROGRAM_3D_FLOAT_BIAS	: PROGRAM_3D_FLOAT;		break;
			case SAMPLERTYPE_INT:		progSpec = useBias ? PROGRAM_3D_INT_BIAS	: PROGRAM_3D_INT;		break;
			case SAMPLERTYPE_UINT:		progSpec = useBias ? PROGRAM_3D_UINT_BIAS	: PROGRAM_3D_UINT;		break;
			default:					DE_ASSERT(false);
		}
	}
	else if (params.texType == TEXTURETYPE_2D_ARRAY)
	{
		DE_ASSERT(!useBias); // \todo [2012-02-17 pyry] Support bias.

		numComps = 3;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FLOAT:		progSpec = PROGRAM_2D_ARRAY_FLOAT;	break;
			case SAMPLERTYPE_INT:		progSpec = PROGRAM_2D_ARRAY_INT;	break;
			case SAMPLERTYPE_UINT:		progSpec = PROGRAM_2D_ARRAY_UINT;	break;
			case SAMPLERTYPE_SHADOW:	progSpec = PROGRAM_2D_ARRAY_SHADOW;	break;
			default:					DE_ASSERT(false);
		}
	}
	else if (params.texType == TEXTURETYPE_CUBE_ARRAY)
	{
		DE_ASSERT(!useBias);

		numComps = 4;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FLOAT:		progSpec = PROGRAM_CUBE_ARRAY_FLOAT;	break;
			case SAMPLERTYPE_INT:		progSpec = PROGRAM_CUBE_ARRAY_INT;		break;
			case SAMPLERTYPE_UINT:		progSpec = PROGRAM_CUBE_ARRAY_UINT;		break;
			case SAMPLERTYPE_SHADOW:	progSpec = PROGRAM_CUBE_ARRAY_SHADOW;	break;
			default:					DE_ASSERT(false);
		}
	}
	else if (params.texType == TEXTURETYPE_1D_ARRAY)
	{
		DE_ASSERT(!useBias); // \todo [2012-02-17 pyry] Support bias.

		numComps = 2;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FLOAT:		progSpec = PROGRAM_1D_ARRAY_FLOAT;	break;
			case SAMPLERTYPE_INT:		progSpec = PROGRAM_1D_ARRAY_INT;	break;
			case SAMPLERTYPE_UINT:		progSpec = PROGRAM_1D_ARRAY_UINT;	break;
			case SAMPLERTYPE_SHADOW:	progSpec = PROGRAM_1D_ARRAY_SHADOW;	break;
			default:					DE_ASSERT(false);
		}
	}
	else if (params.texType == TEXTURETYPE_BUFFER)
	{
		numComps = 1;

		switch (params.samplerType)
		{
			case SAMPLERTYPE_FETCH_FLOAT:	progSpec = PROGRAM_BUFFER_FLOAT;	break;
			case SAMPLERTYPE_FETCH_INT:		progSpec = PROGRAM_BUFFER_INT;		break;
			case SAMPLERTYPE_FETCH_UINT:	progSpec = PROGRAM_BUFFER_UINT;		break;
			default:						DE_ASSERT(false);
		}
	}
	else
		DE_ASSERT(DE_FALSE);

	glu::ShaderProgram* program = m_programLibrary.getProgram(progSpec);

	// \todo [2012-09-26 pyry] Move to ProgramLibrary and log unique programs only(?)
	if (params.flags & RenderParams::LOG_PROGRAMS)
		log << *program;

	GLU_EXPECT_NO_ERROR(gl.getError(), "Set vertex attributes");

	// Program and uniforms.
	deUint32 prog = program->getProgram();
	gl.useProgram(prog);

	gl.uniform1i(gl.getUniformLocation(prog, "u_sampler"), texUnit);
	if (logUniforms)
		log << TestLog::Message << "u_sampler = " << texUnit << TestLog::EndMessage;

	if (useBias)
	{
		gl.uniform1f(gl.getUniformLocation(prog, "u_bias"), params.bias);
		if (logUniforms)
			log << TestLog::Message << "u_bias = " << params.bias << TestLog::EndMessage;
	}

	if (params.samplerType == SAMPLERTYPE_SHADOW)
	{
		gl.uniform1f(gl.getUniformLocation(prog, "u_ref"), params.ref);
		if (logUniforms)
			log << TestLog::Message << "u_ref = " << params.ref << TestLog::EndMessage;
	}

	gl.uniform4fv(gl.getUniformLocation(prog, "u_colorScale"),	1, params.colorScale.getPtr());
	gl.uniform4fv(gl.getUniformLocation(prog, "u_colorBias"),	1, params.colorBias.getPtr());

	if (logUniforms)
	{
		log << TestLog::Message << "u_colorScale = " << params.colorScale << TestLog::EndMessage;
		log << TestLog::Message << "u_colorBias = " << params.colorBias << TestLog::EndMessage;
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "Set program state");

	{
		const glu::VertexArrayBinding vertexArrays[] =
		{
			glu::va::Float("a_position",	4,			4, 0, &position[0]),
			glu::va::Float("a_texCoord",	numComps,	4, 0, texCoord)
		};
		glu::draw(m_renderCtx, prog, DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
				  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
	}
}

void computeQuadTexCoord1D (std::vector<float>& dst, float left, float right)
{
	dst.resize(4);

	dst[0] = left;
	dst[1] = left;
	dst[2] = right;
	dst[3] = right;
}

void computeQuadTexCoord1DArray (std::vector<float>& dst, int layerNdx, float left, float right)
{
	dst.resize(4*2);

	dst[0] = left;	dst[1] = (float)layerNdx;
	dst[2] = left;	dst[3] = (float)layerNdx;
	dst[4] = right;	dst[5] = (float)layerNdx;
	dst[6] = right;	dst[7] = (float)layerNdx;
}

void computeQuadTexCoord2D (std::vector<float>& dst, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight)
{
	dst.resize(4*2);

	dst[0] = bottomLeft.x();	dst[1] = bottomLeft.y();
	dst[2] = bottomLeft.x();	dst[3] = topRight.y();
	dst[4] = topRight.x();		dst[5] = bottomLeft.y();
	dst[6] = topRight.x();		dst[7] = topRight.y();
}

void computeQuadTexCoord2DArray (std::vector<float>& dst, int layerNdx, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight)
{
	dst.resize(4*3);

	dst[0] = bottomLeft.x();	dst[ 1] = bottomLeft.y();	dst[ 2] = (float)layerNdx;
	dst[3] = bottomLeft.x();	dst[ 4] = topRight.y();		dst[ 5] = (float)layerNdx;
	dst[6] = topRight.x();		dst[ 7] = bottomLeft.y();	dst[ 8] = (float)layerNdx;
	dst[9] = topRight.x();		dst[10] = topRight.y();		dst[11] = (float)layerNdx;
}

void computeQuadTexCoord3D (std::vector<float>& dst, const tcu::Vec3& p0, const tcu::Vec3& p1, const tcu::IVec3& dirSwz)
{
	tcu::Vec3 f0 = tcu::Vec3(0.0f, 0.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);
	tcu::Vec3 f1 = tcu::Vec3(0.0f, 1.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);
	tcu::Vec3 f2 = tcu::Vec3(1.0f, 0.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);
	tcu::Vec3 f3 = tcu::Vec3(1.0f, 1.0f, 0.0f).swizzle(dirSwz[0], dirSwz[1], dirSwz[2]);

	tcu::Vec3 v0 = p0 + (p1-p0)*f0;
	tcu::Vec3 v1 = p0 + (p1-p0)*f1;
	tcu::Vec3 v2 = p0 + (p1-p0)*f2;
	tcu::Vec3 v3 = p0 + (p1-p0)*f3;

	dst.resize(4*3);

	dst[0] = v0.x(); dst[ 1] = v0.y(); dst[ 2] = v0.z();
	dst[3] = v1.x(); dst[ 4] = v1.y(); dst[ 5] = v1.z();
	dst[6] = v2.x(); dst[ 7] = v2.y(); dst[ 8] = v2.z();
	dst[9] = v3.x(); dst[10] = v3.y(); dst[11] = v3.z();
}

void computeQuadTexCoordCube (std::vector<float>& dst, tcu::CubeFace face)
{
	static const float texCoordNegX[] =
	{
		-1.0f,  1.0f, -1.0f,
		-1.0f, -1.0f, -1.0f,
		-1.0f,  1.0f,  1.0f,
		-1.0f, -1.0f,  1.0f
	};
	static const float texCoordPosX[] =
	{
		+1.0f,  1.0f,  1.0f,
		+1.0f, -1.0f,  1.0f,
		+1.0f,  1.0f, -1.0f,
		+1.0f, -1.0f, -1.0f
	};
	static const float texCoordNegY[] =
	{
		-1.0f, -1.0f,  1.0f,
		-1.0f, -1.0f, -1.0f,
		 1.0f, -1.0f,  1.0f,
		 1.0f, -1.0f, -1.0f
	};
	static const float texCoordPosY[] =
	{
		-1.0f, +1.0f, -1.0f,
		-1.0f, +1.0f,  1.0f,
		 1.0f, +1.0f, -1.0f,
		 1.0f, +1.0f,  1.0f
	};
	static const float texCoordNegZ[] =
	{
		 1.0f,  1.0f, -1.0f,
		 1.0f, -1.0f, -1.0f,
		-1.0f,  1.0f, -1.0f,
		-1.0f, -1.0f, -1.0f
	};
	static const float texCoordPosZ[] =
	{
		-1.0f,  1.0f, +1.0f,
		-1.0f, -1.0f, +1.0f,
		 1.0f,  1.0f, +1.0f,
		 1.0f, -1.0f, +1.0f
	};

	const float*	texCoord		= DE_NULL;
	int				texCoordSize	= DE_LENGTH_OF_ARRAY(texCoordNegX);

	switch (face)
	{
		case tcu::CUBEFACE_NEGATIVE_X: texCoord = texCoordNegX; break;
		case tcu::CUBEFACE_POSITIVE_X: texCoord = texCoordPosX; break;
		case tcu::CUBEFACE_NEGATIVE_Y: texCoord = texCoordNegY; break;
		case tcu::CUBEFACE_POSITIVE_Y: texCoord = texCoordPosY; break;
		case tcu::CUBEFACE_NEGATIVE_Z: texCoord = texCoordNegZ; break;
		case tcu::CUBEFACE_POSITIVE_Z: texCoord = texCoordPosZ; break;
		default:
			DE_ASSERT(DE_FALSE);
			return;
	}

	dst.resize(texCoordSize);
	std::copy(texCoord, texCoord+texCoordSize, dst.begin());
}

void computeQuadTexCoordCube (std::vector<float>& dst, tcu::CubeFace face, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight)
{
	int		sRow		= 0;
	int		tRow		= 0;
	int		mRow		= 0;
	float	sSign		= 1.0f;
	float	tSign		= 1.0f;
	float	mSign		= 1.0f;

	switch (face)
	{
		case tcu::CUBEFACE_NEGATIVE_X: mRow = 0; sRow = 2; tRow = 1; mSign = -1.0f;				   tSign = -1.0f;	break;
		case tcu::CUBEFACE_POSITIVE_X: mRow = 0; sRow = 2; tRow = 1;				sSign = -1.0f; tSign = -1.0f;	break;
		case tcu::CUBEFACE_NEGATIVE_Y: mRow = 1; sRow = 0; tRow = 2; mSign = -1.0f;				   tSign = -1.0f;	break;
		case tcu::CUBEFACE_POSITIVE_Y: mRow = 1; sRow = 0; tRow = 2;												break;
		case tcu::CUBEFACE_NEGATIVE_Z: mRow = 2; sRow = 0; tRow = 1; mSign = -1.0f; sSign = -1.0f; tSign = -1.0f;	break;
		case tcu::CUBEFACE_POSITIVE_Z: mRow = 2; sRow = 0; tRow = 1;							   tSign = -1.0f;	break;
		default:
			DE_ASSERT(DE_FALSE);
			return;
	}

	dst.resize(3*4);

	dst[0+mRow] = mSign;
	dst[3+mRow] = mSign;
	dst[6+mRow] = mSign;
	dst[9+mRow] = mSign;

	dst[0+sRow] = sSign * bottomLeft.x();
	dst[3+sRow] = sSign * bottomLeft.x();
	dst[6+sRow] = sSign * topRight.x();
	dst[9+sRow] = sSign * topRight.x();

	dst[0+tRow] = tSign * bottomLeft.y();
	dst[3+tRow] = tSign * topRight.y();
	dst[6+tRow] = tSign * bottomLeft.y();
	dst[9+tRow] = tSign * topRight.y();
}

void computeQuadTexCoordCubeArray (std::vector<float>& dst, tcu::CubeFace face, const tcu::Vec2& bottomLeft, const tcu::Vec2& topRight, const tcu::Vec2& layerRange)
{
	int			sRow	= 0;
	int			tRow	= 0;
	int			mRow	= 0;
	const int	qRow	= 3;
	float		sSign	= 1.0f;
	float		tSign	= 1.0f;
	float		mSign	= 1.0f;
	const float	l0		= layerRange.x();
	const float	l1		= layerRange.y();

	switch (face)
	{
		case tcu::CUBEFACE_NEGATIVE_X: mRow = 0; sRow = 2; tRow = 1; mSign = -1.0f;				   tSign = -1.0f;	break;
		case tcu::CUBEFACE_POSITIVE_X: mRow = 0; sRow = 2; tRow = 1;				sSign = -1.0f; tSign = -1.0f;	break;
		case tcu::CUBEFACE_NEGATIVE_Y: mRow = 1; sRow = 0; tRow = 2; mSign = -1.0f;				   tSign = -1.0f;	break;
		case tcu::CUBEFACE_POSITIVE_Y: mRow = 1; sRow = 0; tRow = 2;												break;
		case tcu::CUBEFACE_NEGATIVE_Z: mRow = 2; sRow = 0; tRow = 1; mSign = -1.0f; sSign = -1.0f; tSign = -1.0f;	break;
		case tcu::CUBEFACE_POSITIVE_Z: mRow = 2; sRow = 0; tRow = 1;							   tSign = -1.0f;	break;
		default:
			DE_ASSERT(DE_FALSE);
			return;
	}

	dst.resize(4*4);

	dst[ 0+mRow] = mSign;
	dst[ 4+mRow] = mSign;
	dst[ 8+mRow] = mSign;
	dst[12+mRow] = mSign;

	dst[ 0+sRow] = sSign * bottomLeft.x();
	dst[ 4+sRow] = sSign * bottomLeft.x();
	dst[ 8+sRow] = sSign * topRight.x();
	dst[12+sRow] = sSign * topRight.x();

	dst[ 0+tRow] = tSign * bottomLeft.y();
	dst[ 4+tRow] = tSign * topRight.y();
	dst[ 8+tRow] = tSign * bottomLeft.y();
	dst[12+tRow] = tSign * topRight.y();

	if (l0 != l1)
	{
		dst[ 0+qRow] = l0;
		dst[ 4+qRow] = l0*0.5f + l1*0.5f;
		dst[ 8+qRow] = l0*0.5f + l1*0.5f;
		dst[12+qRow] = l1;
	}
	else
	{
		dst[ 0+qRow] = l0;
		dst[ 4+qRow] = l0;
		dst[ 8+qRow] = l0;
		dst[12+qRow] = l0;
	}
}

// Texture result verification

//! Verifies texture lookup results and returns number of failed pixels.
int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
							  const tcu::ConstPixelBufferAccess&	reference,
							  const tcu::PixelBufferAccess&			errorMask,
							  const tcu::Texture1DView&				baseView,
							  const float*							texCoord,
							  const ReferenceParams&				sampleParams,
							  const tcu::LookupPrecision&			lookupPrec,
							  const tcu::LodPrecision&				lodPrec,
							  qpWatchDog*							watchDog)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Texture1DView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0], texCoord[1], texCoord[2], texCoord[3]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const int			srcSize			= src.getWidth();

	// Coordinates and lod per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		// Ugly hack, validation can take way too long at the moment.
		if (watchDog)
			qpWatchDog_touch(watchDog);

		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;

			// Try comparison to ideal reference first, and if that fails use slower verificator.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
				const float		triWx	= triNdx ? dstW - wx : wx;
				const float		triWy	= triNdx ? dstH - wy : wy;
				const float		triNx	= triNdx ? 1.0f - nx : nx;
				const float		triNy	= triNdx ? 1.0f - ny : ny;

				const float		coord		= projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy);
				const float		coordDx		= triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy) * float(srcSize);
				const float 	coordDy		= triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx) * float(srcSize);

				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx, coordDy, lodPrec);

				// Compute lod bounds across lodOffsets range.
				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
				{
					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
					const float		nxo		= wxo/dstW;
					const float		nyo		= wyo/dstH;

					const float	coordDxo	= triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo) * float(srcSize);
					const float	coordDyo	= triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo) * float(srcSize);
					const tcu::Vec2	lodO	= tcu::computeLodBoundsFromDerivates(coordDxo, coordDyo, lodPrec);

					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
				}

				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
							  const tcu::ConstPixelBufferAccess&	reference,
							  const tcu::PixelBufferAccess&			errorMask,
							  const tcu::Texture2DView&				baseView,
							  const float*							texCoord,
							  const ReferenceParams&					sampleParams,
							  const tcu::LookupPrecision&			lookupPrec,
							  const tcu::LodPrecision&				lodPrec,
							  qpWatchDog*							watchDog)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Texture2DView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const tcu::IVec2	srcSize			= tcu::IVec2(src.getWidth(), src.getHeight());

	// Coordinates and lod per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		// Ugly hack, validation can take way too long at the moment.
		if (watchDog)
			qpWatchDog_touch(watchDog);

		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;

			// Try comparison to ideal reference first, and if that fails use slower verificator.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
				const float		triWx	= triNdx ? dstW - wx : wx;
				const float		triWy	= triNdx ? dstH - wy : wy;
				const float		triNx	= triNdx ? 1.0f - nx : nx;
				const float		triNy	= triNdx ? 1.0f - ny : ny;

				const tcu::Vec2	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy));
				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();

				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);

				// Compute lod bounds across lodOffsets range.
				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
				{
					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
					const float		nxo		= wxo/dstW;
					const float		nyo		= wyo/dstH;

					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);

					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
				}

				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

bool verifyTextureResult (tcu::TestContext&						testCtx,
						  const tcu::ConstPixelBufferAccess&	result,
						  const tcu::Texture1DView&				src,
						  const float*							texCoord,
						  const ReferenceParams&				sampleParams,
						  const tcu::LookupPrecision&			lookupPrec,
						  const tcu::LodPrecision&				lodPrec,
						  const tcu::PixelFormat&				pixelFormat)
{
	tcu::TestLog&	log				= testCtx.getLog();
	tcu::Surface	reference		(result.getWidth(), result.getHeight());
	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
	int				numFailedPixels;

	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);

	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());

	if (numFailedPixels > 0)
		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;

	log << TestLog::ImageSet("VerifyResult", "Verification result")
		<< TestLog::Image("Rendered", "Rendered image", result);

	if (numFailedPixels > 0)
	{
		log << TestLog::Image("Reference", "Ideal reference image", reference)
			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
	}

	log << TestLog::EndImageSet;

	return numFailedPixels == 0;
}

bool verifyTextureResult (tcu::TestContext&						testCtx,
						  const tcu::ConstPixelBufferAccess&	result,
						  const tcu::Texture2DView&				src,
						  const float*							texCoord,
						  const ReferenceParams&				sampleParams,
						  const tcu::LookupPrecision&			lookupPrec,
						  const tcu::LodPrecision&				lodPrec,
						  const tcu::PixelFormat&				pixelFormat)
{
	tcu::TestLog&	log				= testCtx.getLog();
	tcu::Surface	reference		(result.getWidth(), result.getHeight());
	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
	int				numFailedPixels;

	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);

	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());

	if (numFailedPixels > 0)
		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;

	log << TestLog::ImageSet("VerifyResult", "Verification result")
		<< TestLog::Image("Rendered", "Rendered image", result);

	if (numFailedPixels > 0)
	{
		log << TestLog::Image("Reference", "Ideal reference image", reference)
			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
	}

	log << TestLog::EndImageSet;

	return numFailedPixels == 0;
}

//! Verifies texture lookup results and returns number of failed pixels.
int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
							  const tcu::ConstPixelBufferAccess&	reference,
							  const tcu::PixelBufferAccess&			errorMask,
							  const tcu::TextureCubeView&			baseView,
							  const float*							texCoord,
							  const ReferenceParams&				sampleParams,
							  const tcu::LookupPrecision&			lookupPrec,
							  const tcu::LodPrecision&				lodPrec,
							  qpWatchDog*							watchDog)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::TextureCubeView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const int			srcSize			= src.getSize();

	// Coordinates per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	const float			posEps			= 1.0f / float((1<<MIN_SUBPIXEL_BITS) + 1);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),

		// \note Not strictly allowed by spec, but implementations do this in practice.
		tcu::Vec2(-1, -1),
		tcu::Vec2(-1, +1),
		tcu::Vec2(+1, -1),
		tcu::Vec2(+1, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		// Ugly hack, validation can take way too long at the moment.
		if (watchDog)
			qpWatchDog_touch(watchDog);

		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;

			// Try comparison to ideal reference first, and if that fails use slower verificator.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const bool		tri0	= nx + ny - posEps <= 1.0f;
				const bool		tri1	= nx + ny + posEps >= 1.0f;

				bool			isOk	= false;

				DE_ASSERT(tri0 || tri1);

				// Pixel can belong to either of the triangles if it lies close enough to the edge.
				for (int triNdx = (tri0?0:1); triNdx <= (tri1?1:0); triNdx++)
				{
					const float		triWx	= triNdx ? dstW - wx : wx;
					const float		triWy	= triNdx ? dstH - wy : wy;
					const float		triNx	= triNdx ? 1.0f - nx : nx;
					const float		triNy	= triNdx ? 1.0f - ny : ny;

					const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
												 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
					const tcu::Vec3	coordDx		(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
												 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
												 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
					const tcu::Vec3	coordDy		(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
												 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
												 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));

					tcu::Vec2		lodBounds	= tcu::computeCubeLodBoundsFromDerivates(coord, coordDx, coordDy, srcSize, lodPrec);

					// Compute lod bounds across lodOffsets range.
					for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
					{
						const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
						const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
						const float		nxo		= wxo/dstW;
						const float		nyo		= wyo/dstH;

						const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
													 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
													 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
						const tcu::Vec3	coordDxo	(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
													 triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo),
													 triDerivateX(triR[triNdx], triW[triNdx], wxo, dstW, nyo));
						const tcu::Vec3	coordDyo	(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
													 triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo),
													 triDerivateY(triR[triNdx], triW[triNdx], wyo, dstH, nxo));
						const tcu::Vec2	lodO		= tcu::computeCubeLodBoundsFromDerivates(coordO, coordDxo, coordDyo, srcSize, lodPrec);

						lodBounds.x() = de::min(lodBounds.x(), lodO.x());
						lodBounds.y() = de::max(lodBounds.y(), lodO.y());
					}

					const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);

					if (tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix))
					{
						isOk = true;
						break;
					}
				}

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

bool verifyTextureResult (tcu::TestContext&						testCtx,
						  const tcu::ConstPixelBufferAccess&	result,
						  const tcu::TextureCubeView&			src,
						  const float*							texCoord,
						  const ReferenceParams&				sampleParams,
						  const tcu::LookupPrecision&			lookupPrec,
						  const tcu::LodPrecision&				lodPrec,
						  const tcu::PixelFormat&				pixelFormat)
{
	tcu::TestLog&	log				= testCtx.getLog();
	tcu::Surface	reference		(result.getWidth(), result.getHeight());
	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
	int				numFailedPixels;

	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);

	sampleTextureMultiFace(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());

	if (numFailedPixels > 0)
		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;

	log << TestLog::ImageSet("VerifyResult", "Verification result")
		<< TestLog::Image("Rendered", "Rendered image", result);

	if (numFailedPixels > 0)
	{
		log << TestLog::Image("Reference", "Ideal reference image", reference)
			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
	}

	log << TestLog::EndImageSet;

	return numFailedPixels == 0;
}

//! Verifies texture lookup results and returns number of failed pixels.
int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
							  const tcu::ConstPixelBufferAccess&	reference,
							  const tcu::PixelBufferAccess&			errorMask,
							  const tcu::Texture3DView&				baseView,
							  const float*							texCoord,
							  const ReferenceParams&				sampleParams,
							  const tcu::LookupPrecision&			lookupPrec,
							  const tcu::LodPrecision&				lodPrec,
							  qpWatchDog*							watchDog)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Texture3DView	src		= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const tcu::IVec3	srcSize			= tcu::IVec3(src.getWidth(), src.getHeight(), src.getDepth());

	// Coordinates and lod per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	const float			posEps			= 1.0f / float((1<<MIN_SUBPIXEL_BITS) + 1);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		// Ugly hack, validation can take way too long at the moment.
		if (watchDog)
			qpWatchDog_touch(watchDog);

		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;

			// Try comparison to ideal reference first, and if that fails use slower verificator.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const bool		tri0	= nx + ny - posEps <= 1.0f;
				const bool		tri1	= nx + ny + posEps >= 1.0f;

				bool			isOk	= false;

				DE_ASSERT(tri0 || tri1);

				// Pixel can belong to either of the triangles if it lies close enough to the edge.
				for (int triNdx = (tri0?0:1); triNdx <= (tri1?1:0); triNdx++)
				{
					const float		triWx	= triNdx ? dstW - wx : wx;
					const float		triWy	= triNdx ? dstH - wy : wy;
					const float		triNx	= triNdx ? 1.0f - nx : nx;
					const float		triNy	= triNdx ? 1.0f - ny : ny;

					const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
												 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
					const tcu::Vec3	coordDx		= tcu::Vec3(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
															triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
															triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
					const tcu::Vec3	coordDy		= tcu::Vec3(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
															triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
															triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();

					tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDx.z(), coordDy.x(), coordDy.y(), coordDy.z(), lodPrec);

					// Compute lod bounds across lodOffsets range.
					for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
					{
						const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
						const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
						const float		nxo		= wxo/dstW;
						const float		nyo		= wyo/dstH;

						const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
													 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
													 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
						const tcu::Vec3	coordDxo	= tcu::Vec3(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
																triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo),
																triDerivateX(triR[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
						const tcu::Vec3	coordDyo	= tcu::Vec3(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
																triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo),
																triDerivateY(triR[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
						const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDxo.z(), coordDyo.x(), coordDyo.y(), coordDyo.z(), lodPrec);

						lodBounds.x() = de::min(lodBounds.x(), lodO.x());
						lodBounds.y() = de::max(lodBounds.y(), lodO.y());
					}

					const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);

					if (tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix))
					{
						isOk = true;
						break;
					}
				}

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

bool verifyTextureResult (tcu::TestContext&						testCtx,
						  const tcu::ConstPixelBufferAccess&	result,
						  const tcu::Texture3DView&				src,
						  const float*							texCoord,
						  const ReferenceParams&				sampleParams,
						  const tcu::LookupPrecision&			lookupPrec,
						  const tcu::LodPrecision&				lodPrec,
						  const tcu::PixelFormat&				pixelFormat)
{
	tcu::TestLog&	log				= testCtx.getLog();
	tcu::Surface	reference		(result.getWidth(), result.getHeight());
	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
	int				numFailedPixels;

	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);

	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());

	if (numFailedPixels > 0)
		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;

	log << TestLog::ImageSet("VerifyResult", "Verification result")
		<< TestLog::Image("Rendered", "Rendered image", result);

	if (numFailedPixels > 0)
	{
		log << TestLog::Image("Reference", "Ideal reference image", reference)
			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
	}

	log << TestLog::EndImageSet;

	return numFailedPixels == 0;
}

//! Verifies texture lookup results and returns number of failed pixels.
int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
							  const tcu::ConstPixelBufferAccess&	reference,
							  const tcu::PixelBufferAccess&			errorMask,
							  const tcu::Texture1DArrayView&		src,
							  const float*							texCoord,
							  const ReferenceParams&				sampleParams,
							  const tcu::LookupPrecision&			lookupPrec,
							  const tcu::LodPrecision&				lodPrec,
							  qpWatchDog*							watchDog)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const float			srcSize			= float(src.getWidth()); // For lod computation, thus #layers is ignored.

	// Coordinates and lod per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		// Ugly hack, validation can take way too long at the moment.
		if (watchDog)
			qpWatchDog_touch(watchDog);

		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;

			// Try comparison to ideal reference first, and if that fails use slower verificator.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
				const float		triWx	= triNdx ? dstW - wx : wx;
				const float		triWy	= triNdx ? dstH - wy : wy;
				const float		triNx	= triNdx ? 1.0f - nx : nx;
				const float		triNy	= triNdx ? 1.0f - ny : ny;

				const tcu::Vec2	coord	(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
										 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy));
				const float	coordDx		= triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy) * srcSize;
				const float	coordDy		= triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx) * srcSize;

				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx, coordDy, lodPrec);

				// Compute lod bounds across lodOffsets range.
				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
				{
					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
					const float		nxo		= wxo/dstW;
					const float		nyo		= wyo/dstH;

					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
					const float	coordDxo		= triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo) * srcSize;
					const float	coordDyo		= triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo) * srcSize;
					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo, coordDyo, lodPrec);

					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
				}

				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

//! Verifies texture lookup results and returns number of failed pixels.
int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
							  const tcu::ConstPixelBufferAccess&	reference,
							  const tcu::PixelBufferAccess&			errorMask,
							  const tcu::Texture2DArrayView&		src,
							  const float*							texCoord,
							  const ReferenceParams&				sampleParams,
							  const tcu::LookupPrecision&			lookupPrec,
							  const tcu::LodPrecision&				lodPrec,
							  qpWatchDog*							watchDog)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const tcu::Vec2		srcSize			= tcu::IVec2(src.getWidth(), src.getHeight()).asFloat(); // For lod computation, thus #layers is ignored.

	// Coordinates and lod per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		// Ugly hack, validation can take way too long at the moment.
		if (watchDog)
			qpWatchDog_touch(watchDog);

		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;

			// Try comparison to ideal reference first, and if that fails use slower verificator.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
				const float		triWx	= triNdx ? dstW - wx : wx;
				const float		triWy	= triNdx ? dstH - wy : wy;
				const float		triNx	= triNdx ? 1.0f - nx : nx;
				const float		triNy	= triNdx ? 1.0f - ny : ny;

				const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize;
				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize;

				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);

				// Compute lod bounds across lodOffsets range.
				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
				{
					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
					const float		nxo		= wxo/dstW;
					const float		nyo		= wyo/dstH;

					const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize;
					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize;
					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);

					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
				}

				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
				const bool		isOk		= tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix);

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

bool verifyTextureResult (tcu::TestContext&						testCtx,
						  const tcu::ConstPixelBufferAccess&	result,
						  const tcu::Texture1DArrayView&		src,
						  const float*							texCoord,
						  const ReferenceParams&				sampleParams,
						  const tcu::LookupPrecision&			lookupPrec,
						  const tcu::LodPrecision&				lodPrec,
						  const tcu::PixelFormat&				pixelFormat)
{
	tcu::TestLog&	log				= testCtx.getLog();
	tcu::Surface	reference		(result.getWidth(), result.getHeight());
	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
	int				numFailedPixels;

	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);

	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());

	if (numFailedPixels > 0)
		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;

	log << TestLog::ImageSet("VerifyResult", "Verification result")
		<< TestLog::Image("Rendered", "Rendered image", result);

	if (numFailedPixels > 0)
	{
		log << TestLog::Image("Reference", "Ideal reference image", reference)
			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
	}

	log << TestLog::EndImageSet;

	return numFailedPixels == 0;
}

bool verifyTextureResult (tcu::TestContext&						testCtx,
						  const tcu::ConstPixelBufferAccess&	result,
						  const tcu::Texture2DArrayView&		src,
						  const float*							texCoord,
						  const ReferenceParams&				sampleParams,
						  const tcu::LookupPrecision&			lookupPrec,
						  const tcu::LodPrecision&				lodPrec,
						  const tcu::PixelFormat&				pixelFormat)
{
	tcu::TestLog&	log				= testCtx.getLog();
	tcu::Surface	reference		(result.getWidth(), result.getHeight());
	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
	int				numFailedPixels;

	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);

	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, lodPrec, testCtx.getWatchDog());

	if (numFailedPixels > 0)
		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;

	log << TestLog::ImageSet("VerifyResult", "Verification result")
		<< TestLog::Image("Rendered", "Rendered image", result);

	if (numFailedPixels > 0)
	{
		log << TestLog::Image("Reference", "Ideal reference image", reference)
			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
	}

	log << TestLog::EndImageSet;

	return numFailedPixels == 0;
}

//! Verifies texture lookup results and returns number of failed pixels.
int computeTextureLookupDiff (const tcu::ConstPixelBufferAccess&	result,
							  const tcu::ConstPixelBufferAccess&	reference,
							  const tcu::PixelBufferAccess&			errorMask,
							  const tcu::TextureCubeArrayView&		baseView,
							  const float*							texCoord,
							  const ReferenceParams&				sampleParams,
							  const tcu::LookupPrecision&			lookupPrec,
							  const tcu::IVec4&						coordBits,
							  const tcu::LodPrecision&				lodPrec,
							  qpWatchDog*							watchDog)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::TextureCubeArrayView	src	= getSubView(baseView, sampleParams.baseLevel, sampleParams.maxLevel);

	// What is the 'q' in all these names? Also a two char name for something that is in scope for ~120 lines and only used twice each seems excessive
	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[4+0], texCoord[8+0], texCoord[12+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[4+1], texCoord[8+1], texCoord[12+1]);
	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[4+2], texCoord[8+2], texCoord[12+2]);
	const tcu::Vec4		qq				= tcu::Vec4(texCoord[0+3], texCoord[4+3], texCoord[8+3], texCoord[12+3]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const int			srcSize			= src.getSize();

	// Coordinates per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	const tcu::Vec3		triQ[2]			= { qq.swizzle(0, 1, 2), qq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	const float			posEps			= 1.0f / float((1<<4) + 1); // ES3 requires at least 4 subpixel bits.

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),

		// \note Not strictly allowed by spec, but implementations do this in practice.
		tcu::Vec2(-1, -1),
		tcu::Vec2(-1, +1),
		tcu::Vec2(+1, -1),
		tcu::Vec2(+1, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		// Ugly hack, validation can take way too long at the moment.
		if (watchDog)
			qpWatchDog_touch(watchDog);

		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= (result.getPixel(px, py)		- sampleParams.colorBias) / sampleParams.colorScale;
			const tcu::Vec4	refPix	= (reference.getPixel(px, py)	- sampleParams.colorBias) / sampleParams.colorScale;

			// Try comparison to ideal reference first, and if that fails use slower verificator.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(resPix - refPix), lookupPrec.colorThreshold)))
			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const bool		tri0	= nx + ny - posEps <= 1.0f;
				const bool		tri1	= nx + ny + posEps >= 1.0f;

				bool			isOk	= false;

				DE_ASSERT(tri0 || tri1);

				// Pixel can belong to either of the triangles if it lies close enough to the edge.
				for (int triNdx = (tri0?0:1); triNdx <= (tri1?1:0); triNdx++)
				{
					const float		triWx		= triNdx ? dstW - wx : wx;
					const float		triWy		= triNdx ? dstH - wy : wy;
					const float		triNx		= triNdx ? 1.0f - nx : nx;
					const float		triNy		= triNdx ? 1.0f - ny : ny;

					const tcu::Vec4	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
												 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy),
												 projectedTriInterpolate(triQ[triNdx], triW[triNdx], triNx, triNy));
					const tcu::Vec3	coordDx		(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
												 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
												 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
					const tcu::Vec3	coordDy		(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
												 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
												 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));

					tcu::Vec2		lodBounds	= tcu::computeCubeLodBoundsFromDerivates(coord.toWidth<3>(), coordDx, coordDy, srcSize, lodPrec);

					// Compute lod bounds across lodOffsets range.
					for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
					{
						const float		wxo			= triWx + lodOffsets[lodOffsNdx].x();
						const float		wyo			= triWy + lodOffsets[lodOffsNdx].y();
						const float		nxo			= wxo/dstW;
						const float		nyo			= wyo/dstH;

						const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
													 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
													 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
						const tcu::Vec3	coordDxo	(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
													 triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo),
													 triDerivateX(triR[triNdx], triW[triNdx], wxo, dstW, nyo));
						const tcu::Vec3	coordDyo	(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
													 triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo),
													 triDerivateY(triR[triNdx], triW[triNdx], wyo, dstH, nxo));
						const tcu::Vec2	lodO		= tcu::computeCubeLodBoundsFromDerivates(coordO, coordDxo, coordDyo, srcSize, lodPrec);

						lodBounds.x() = de::min(lodBounds.x(), lodO.x());
						lodBounds.y() = de::max(lodBounds.y(), lodO.y());
					}

					const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);

					if (tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coordBits, coord, clampedLod, resPix))
					{
						isOk = true;
						break;
					}
				}

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

bool verifyTextureResult (tcu::TestContext&						testCtx,
						  const tcu::ConstPixelBufferAccess&	result,
						  const tcu::TextureCubeArrayView&		src,
						  const float*							texCoord,
						  const ReferenceParams&				sampleParams,
						  const tcu::LookupPrecision&			lookupPrec,
						  const tcu::IVec4&						coordBits,
						  const tcu::LodPrecision&				lodPrec,
						  const tcu::PixelFormat&				pixelFormat)
{
	tcu::TestLog&	log				= testCtx.getLog();
	tcu::Surface	reference		(result.getWidth(), result.getHeight());
	tcu::Surface	errorMask		(result.getWidth(), result.getHeight());
	int				numFailedPixels;

	DE_ASSERT(getCompareMask(pixelFormat) == lookupPrec.colorMask);

	sampleTexture(SurfaceAccess(reference, pixelFormat), src, texCoord, sampleParams);
	numFailedPixels = computeTextureLookupDiff(result, reference.getAccess(), errorMask.getAccess(), src, texCoord, sampleParams, lookupPrec, coordBits, lodPrec, testCtx.getWatchDog());

	if (numFailedPixels > 0)
		log << TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels << " invalid pixels!" << TestLog::EndMessage;

	log << TestLog::ImageSet("VerifyResult", "Verification result")
		<< TestLog::Image("Rendered", "Rendered image", result);

	if (numFailedPixels > 0)
	{
		log << TestLog::Image("Reference", "Ideal reference image", reference)
			<< TestLog::Image("ErrorMask", "Error mask", errorMask);
	}

	log << TestLog::EndImageSet;

	return numFailedPixels == 0;
}

// Shadow lookup verification

int computeTextureCompareDiff (const tcu::ConstPixelBufferAccess&	result,
							   const tcu::ConstPixelBufferAccess&	reference,
							   const tcu::PixelBufferAccess&		errorMask,
							   const tcu::Texture2DView&			src,
							   const float*							texCoord,
							   const ReferenceParams&				sampleParams,
							   const tcu::TexComparePrecision&		comparePrec,
							   const tcu::LodPrecision&				lodPrec,
							   const tcu::Vec3&						nonShadowThreshold)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[2+0], texCoord[4+0], texCoord[6+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[2+1], texCoord[4+1], texCoord[6+1]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const tcu::IVec2	srcSize			= tcu::IVec2(src.getWidth(), src.getHeight());

	// Coordinates and lod per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= result.getPixel(px, py);
			const tcu::Vec4	refPix	= reference.getPixel(px, py);

			// Other channels should trivially match to reference.
			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(refPix.swizzle(1,2,3) - resPix.swizzle(1,2,3)), nonShadowThreshold)))
			{
				errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
				numFailed += 1;
				continue;
			}

			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
				const float		triWx	= triNdx ? dstW - wx : wx;
				const float		triWy	= triNdx ? dstH - wy : wy;
				const float		triNx	= triNdx ? 1.0f - nx : nx;
				const float		triNy	= triNdx ? 1.0f - ny : ny;

				const tcu::Vec2	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy));
				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();

				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);

				// Compute lod bounds across lodOffsets range.
				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
				{
					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
					const float		nxo		= wxo/dstW;
					const float		nyo		= wyo/dstH;

					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);

					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
				}

				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
				const bool		isOk		= tcu::isTexCompareResultValid(src, sampleParams.sampler, comparePrec, coord, clampedLod, sampleParams.ref, resPix.x());

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

int computeTextureCompareDiff (const tcu::ConstPixelBufferAccess&	result,
							   const tcu::ConstPixelBufferAccess&	reference,
							   const tcu::PixelBufferAccess&		errorMask,
							   const tcu::TextureCubeView&			src,
							   const float*							texCoord,
							   const ReferenceParams&				sampleParams,
							   const tcu::TexComparePrecision&		comparePrec,
							   const tcu::LodPrecision&				lodPrec,
							   const tcu::Vec3&						nonShadowThreshold)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const int			srcSize			= src.getSize();

	// Coordinates per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= result.getPixel(px, py);
			const tcu::Vec4	refPix	= reference.getPixel(px, py);

			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(refPix.swizzle(1,2,3) - resPix.swizzle(1,2,3)), nonShadowThreshold)))
			{
				errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
				numFailed += 1;
				continue;
			}

			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
				const float		triWx	= triNdx ? dstW - wx : wx;
				const float		triWy	= triNdx ? dstH - wy : wy;
				const float		triNx	= triNdx ? 1.0f - nx : nx;
				const float		triNy	= triNdx ? 1.0f - ny : ny;

				const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
				const tcu::Vec3	coordDx		(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
											 triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy),
											 triDerivateX(triR[triNdx], triW[triNdx], wx, dstW, triNy));
				const tcu::Vec3	coordDy		(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
											 triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx),
											 triDerivateY(triR[triNdx], triW[triNdx], wy, dstH, triNx));

				tcu::Vec2		lodBounds	= tcu::computeCubeLodBoundsFromDerivates(coord, coordDx, coordDy, srcSize, lodPrec);

				// Compute lod bounds across lodOffsets range.
				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
				{
					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
					const float		nxo		= wxo/dstW;
					const float		nyo		= wyo/dstH;

					const tcu::Vec3	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triR[triNdx], triW[triNdx], nxo, nyo));
					const tcu::Vec3	coordDxo	(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
												 triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo),
												 triDerivateX(triR[triNdx], triW[triNdx], wxo, dstW, nyo));
					const tcu::Vec3	coordDyo	(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
												 triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo),
												 triDerivateY(triR[triNdx], triW[triNdx], wyo, dstH, nxo));
					const tcu::Vec2	lodO		= tcu::computeCubeLodBoundsFromDerivates(coordO, coordDxo, coordDyo, srcSize, lodPrec);

					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
				}

				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
				const bool		isOk		= tcu::isTexCompareResultValid(src, sampleParams.sampler, comparePrec, coord, clampedLod, sampleParams.ref, resPix.x());

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

int computeTextureCompareDiff (const tcu::ConstPixelBufferAccess&	result,
							   const tcu::ConstPixelBufferAccess&	reference,
							   const tcu::PixelBufferAccess&		errorMask,
							   const tcu::Texture2DArrayView&		src,
							   const float*							texCoord,
							   const ReferenceParams&				sampleParams,
							   const tcu::TexComparePrecision&		comparePrec,
							   const tcu::LodPrecision&				lodPrec,
							   const tcu::Vec3&						nonShadowThreshold)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());
	DE_ASSERT(result.getWidth() == errorMask.getWidth() && result.getHeight() == errorMask.getHeight());

	const tcu::Vec4		sq				= tcu::Vec4(texCoord[0+0], texCoord[3+0], texCoord[6+0], texCoord[9+0]);
	const tcu::Vec4		tq				= tcu::Vec4(texCoord[0+1], texCoord[3+1], texCoord[6+1], texCoord[9+1]);
	const tcu::Vec4		rq				= tcu::Vec4(texCoord[0+2], texCoord[3+2], texCoord[6+2], texCoord[9+2]);

	const tcu::IVec2	dstSize			= tcu::IVec2(result.getWidth(), result.getHeight());
	const float			dstW			= float(dstSize.x());
	const float			dstH			= float(dstSize.y());
	const tcu::IVec2	srcSize			= tcu::IVec2(src.getWidth(), src.getHeight());

	// Coordinates and lod per triangle.
	const tcu::Vec3		triS[2]			= { sq.swizzle(0, 1, 2), sq.swizzle(3, 2, 1) };
	const tcu::Vec3		triT[2]			= { tq.swizzle(0, 1, 2), tq.swizzle(3, 2, 1) };
	const tcu::Vec3		triR[2]			= { rq.swizzle(0, 1, 2), rq.swizzle(3, 2, 1) };
	const tcu::Vec3		triW[2]			= { sampleParams.w.swizzle(0, 1, 2), sampleParams.w.swizzle(3, 2, 1) };

	const tcu::Vec2		lodBias			((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);

	int					numFailed		= 0;

	const tcu::Vec2 lodOffsets[] =
	{
		tcu::Vec2(-1,  0),
		tcu::Vec2(+1,  0),
		tcu::Vec2( 0, -1),
		tcu::Vec2( 0, +1),
	};

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

	for (int py = 0; py < result.getHeight(); py++)
	{
		for (int px = 0; px < result.getWidth(); px++)
		{
			const tcu::Vec4	resPix	= result.getPixel(px, py);
			const tcu::Vec4	refPix	= reference.getPixel(px, py);

			if (!tcu::boolAll(tcu::lessThanEqual(tcu::abs(refPix.swizzle(1,2,3) - resPix.swizzle(1,2,3)), nonShadowThreshold)))
			{
				errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
				numFailed += 1;
				continue;
			}

			{
				const float		wx		= (float)px + 0.5f;
				const float		wy		= (float)py + 0.5f;
				const float		nx		= wx / dstW;
				const float		ny		= wy / dstH;

				const int		triNdx	= nx + ny >= 1.0f ? 1 : 0;
				const float		triWx	= triNdx ? dstW - wx : wx;
				const float		triWy	= triNdx ? dstH - wy : wy;
				const float		triNx	= triNdx ? 1.0f - nx : nx;
				const float		triNy	= triNdx ? 1.0f - ny : ny;

				const tcu::Vec3	coord		(projectedTriInterpolate(triS[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triT[triNdx], triW[triNdx], triNx, triNy),
											 projectedTriInterpolate(triR[triNdx], triW[triNdx], triNx, triNy));
				const tcu::Vec2	coordDx		= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wx, dstW, triNy),
														triDerivateX(triT[triNdx], triW[triNdx], wx, dstW, triNy)) * srcSize.asFloat();
				const tcu::Vec2	coordDy		= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wy, dstH, triNx),
														triDerivateY(triT[triNdx], triW[triNdx], wy, dstH, triNx)) * srcSize.asFloat();

				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);

				// Compute lod bounds across lodOffsets range.
				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
				{
					const float		wxo		= triWx + lodOffsets[lodOffsNdx].x();
					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
					const float		nxo		= wxo/dstW;
					const float		nyo		= wyo/dstH;

					const tcu::Vec2	coordO		(projectedTriInterpolate(triS[triNdx], triW[triNdx], nxo, nyo),
												 projectedTriInterpolate(triT[triNdx], triW[triNdx], nxo, nyo));
					const tcu::Vec2	coordDxo	= tcu::Vec2(triDerivateX(triS[triNdx], triW[triNdx], wxo, dstW, nyo),
															triDerivateX(triT[triNdx], triW[triNdx], wxo, dstW, nyo)) * srcSize.asFloat();
					const tcu::Vec2	coordDyo	= tcu::Vec2(triDerivateY(triS[triNdx], triW[triNdx], wyo, dstH, nxo),
															triDerivateY(triT[triNdx], triW[triNdx], wyo, dstH, nxo)) * srcSize.asFloat();
					const tcu::Vec2	lodO		= tcu::computeLodBoundsFromDerivates(coordDxo.x(), coordDxo.y(), coordDyo.x(), coordDyo.y(), lodPrec);

					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
				}

				const tcu::Vec2	clampedLod	= tcu::clampLodBounds(lodBounds + lodBias, tcu::Vec2(sampleParams.minLod, sampleParams.maxLod), lodPrec);
				const bool		isOk		= tcu::isTexCompareResultValid(src, sampleParams.sampler, comparePrec, coord, clampedLod, sampleParams.ref, resPix.x());

				if (!isOk)
				{
					errorMask.setPixel(tcu::RGBA::red.toVec(), px, py);
					numFailed += 1;
				}
			}
		}
	}

	return numFailed;
}

// Mipmap generation comparison.

static int compareGenMipmapBilinear (const tcu::ConstPixelBufferAccess& dst, const tcu::ConstPixelBufferAccess& src, const tcu::PixelBufferAccess& errorMask, const GenMipmapPrecision& precision)
{
	DE_ASSERT(dst.getDepth() == 1 && src.getDepth() == 1); // \todo [2013-10-29 pyry] 3D textures.

	const float		dstW		= float(dst.getWidth());
	const float		dstH		= float(dst.getHeight());
	const float		srcW		= float(src.getWidth());
	const float		srcH		= float(src.getHeight());
	int				numFailed	= 0;

	// Translation to lookup verification parameters.
	const tcu::Sampler		sampler		(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
										 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR, 0.0f, false /* non-normalized coords */);
	tcu::LookupPrecision	lookupPrec;

	lookupPrec.colorThreshold	= precision.colorThreshold;
	lookupPrec.colorMask		= precision.colorMask;
	lookupPrec.coordBits		= tcu::IVec3(22);
	lookupPrec.uvwBits			= precision.filterBits;

	for (int y = 0; y < dst.getHeight(); y++)
	for (int x = 0; x < dst.getWidth(); x++)
	{
		const tcu::Vec4	result	= dst.getPixel(x, y);
		const float		cx		= (float(x)+0.5f) / dstW * srcW;
		const float		cy		= (float(y)+0.5f) / dstH * srcH;
		const bool		isOk	= tcu::isLinearSampleResultValid(src, sampler, lookupPrec, tcu::Vec2(cx, cy), 0, result);

		errorMask.setPixel(isOk ? tcu::RGBA::green.toVec() : tcu::RGBA::red.toVec(), x, y);
		if (!isOk)
			numFailed += 1;
	}

	return numFailed;
}

static int compareGenMipmapBox (const tcu::ConstPixelBufferAccess& dst, const tcu::ConstPixelBufferAccess& src, const tcu::PixelBufferAccess& errorMask, const GenMipmapPrecision& precision)
{
	DE_ASSERT(dst.getDepth() == 1 && src.getDepth() == 1); // \todo [2013-10-29 pyry] 3D textures.

	const float		dstW		= float(dst.getWidth());
	const float		dstH		= float(dst.getHeight());
	const float		srcW		= float(src.getWidth());
	const float		srcH		= float(src.getHeight());
	int				numFailed	= 0;

	// Translation to lookup verification parameters.
	const tcu::Sampler		sampler		(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
										 tcu::Sampler::LINEAR, tcu::Sampler::LINEAR, 0.0f, false /* non-normalized coords */);
	tcu::LookupPrecision	lookupPrec;

	lookupPrec.colorThreshold	= precision.colorThreshold;
	lookupPrec.colorMask		= precision.colorMask;
	lookupPrec.coordBits		= tcu::IVec3(22);
	lookupPrec.uvwBits			= precision.filterBits;

	for (int y = 0; y < dst.getHeight(); y++)
	for (int x = 0; x < dst.getWidth(); x++)
	{
		const tcu::Vec4	result	= dst.getPixel(x, y);
		const float		cx		= deFloatFloor(float(x) / dstW * srcW) + 1.0f;
		const float		cy		= deFloatFloor(float(y) / dstH * srcH) + 1.0f;
		const bool		isOk	= tcu::isLinearSampleResultValid(src, sampler, lookupPrec, tcu::Vec2(cx, cy), 0, result);

		errorMask.setPixel(isOk ? tcu::RGBA::green.toVec() : tcu::RGBA::red.toVec(), x, y);
		if (!isOk)
			numFailed += 1;
	}

	return numFailed;
}

static int compareGenMipmapVeryLenient (const tcu::ConstPixelBufferAccess& dst, const tcu::ConstPixelBufferAccess& src, const tcu::PixelBufferAccess& errorMask, const GenMipmapPrecision& precision)
{
	DE_ASSERT(dst.getDepth() == 1 && src.getDepth() == 1); // \todo [2013-10-29 pyry] 3D textures.
	DE_UNREF(precision);

	const float		dstW		= float(dst.getWidth());
	const float		dstH		= float(dst.getHeight());
	const float		srcW		= float(src.getWidth());
	const float		srcH		= float(src.getHeight());
	int				numFailed	= 0;

	for (int y = 0; y < dst.getHeight(); y++)
	for (int x = 0; x < dst.getWidth(); x++)
	{
		const tcu::Vec4	result	= dst.getPixel(x, y);
		const int		minX		= deFloorFloatToInt32(float(x-0.5f) / dstW * srcW);
		const int		minY		= deFloorFloatToInt32(float(y-0.5f) / dstH * srcH);
		const int		maxX		= deCeilFloatToInt32(float(x+1.5f) / dstW * srcW);
		const int		maxY		= deCeilFloatToInt32(float(y+1.5f) / dstH * srcH);
		tcu::Vec4		minVal, maxVal;
		bool			isOk;

		DE_ASSERT(minX < maxX && minY < maxY);

		for (int ky = minY; ky <= maxY; ky++)
		{
			for (int kx = minX; kx <= maxX; kx++)
			{
				const int		sx		= de::clamp(kx, 0, src.getWidth()-1);
				const int		sy		= de::clamp(ky, 0, src.getHeight()-1);
				const tcu::Vec4	sample	= src.getPixel(sx, sy);

				if (ky == minY && kx == minX)
				{
					minVal = sample;
					maxVal = sample;
				}
				else
				{
					minVal = min(sample, minVal);
					maxVal = max(sample, maxVal);
				}
			}
		}

		isOk = boolAll(logicalAnd(lessThanEqual(minVal, result), lessThanEqual(result, maxVal)));

		errorMask.setPixel(isOk ? tcu::RGBA::green.toVec() : tcu::RGBA::red.toVec(), x, y);
		if (!isOk)
			numFailed += 1;
	}

	return numFailed;
}

qpTestResult compareGenMipmapResult (tcu::TestLog& log, const tcu::Texture2D& resultTexture, const tcu::Texture2D& level0Reference, const GenMipmapPrecision& precision)
{
	qpTestResult result = QP_TEST_RESULT_PASS;

	// Special comparison for level 0.
	{
		const tcu::Vec4		threshold	= select(precision.colorThreshold, tcu::Vec4(1.0f), precision.colorMask);
		const bool			level0Ok	= tcu::floatThresholdCompare(log, "Level0", "Level 0", level0Reference.getLevel(0), resultTexture.getLevel(0), threshold, tcu::COMPARE_LOG_RESULT);

		if (!level0Ok)
		{
			log << TestLog::Message << "ERROR: Level 0 comparison failed!" << TestLog::EndMessage;
			result = QP_TEST_RESULT_FAIL;
		}
	}

	for (int levelNdx = 1; levelNdx < resultTexture.getNumLevels(); levelNdx++)
	{
		const tcu::ConstPixelBufferAccess	src			= resultTexture.getLevel(levelNdx-1);
		const tcu::ConstPixelBufferAccess	dst			= resultTexture.getLevel(levelNdx);
		tcu::Surface						errorMask	(dst.getWidth(), dst.getHeight());
		bool								levelOk		= false;

		// Try different comparisons in quality order.

		if (!levelOk)
		{
			const int numFailed = compareGenMipmapBilinear(dst, src, errorMask.getAccess(), precision);
			if (numFailed == 0)
				levelOk = true;
			else
				log << TestLog::Message << "WARNING: Level " << levelNdx << " comparison to bilinear method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
		}

		if (!levelOk)
		{
			const int numFailed = compareGenMipmapBox(dst, src, errorMask.getAccess(), precision);
			if (numFailed == 0)
				levelOk = true;
			else
				log << TestLog::Message << "WARNING: Level " << levelNdx << " comparison to box method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
		}

		// At this point all high-quality methods have been used.
		if (!levelOk && result == QP_TEST_RESULT_PASS)
			result = QP_TEST_RESULT_QUALITY_WARNING;

		if (!levelOk)
		{
			const int numFailed = compareGenMipmapVeryLenient(dst, src, errorMask.getAccess(), precision);
			if (numFailed == 0)
				levelOk = true;
			else
				log << TestLog::Message << "ERROR: Level " << levelNdx << " appears to contain " << numFailed << " completely wrong pixels, failing case!" << TestLog::EndMessage;
		}

		if (!levelOk)
			result = QP_TEST_RESULT_FAIL;

		log << TestLog::ImageSet(string("Level") + de::toString(levelNdx), string("Level ") + de::toString(levelNdx) + " result")
			<< TestLog::Image("Result", "Result", dst);

		if (!levelOk)
			log << TestLog::Image("ErrorMask", "Error mask", errorMask);

		log << TestLog::EndImageSet;
	}

	return result;
}

qpTestResult compareGenMipmapResult (tcu::TestLog& log, const tcu::TextureCube& resultTexture, const tcu::TextureCube& level0Reference, const GenMipmapPrecision& precision)
{
	qpTestResult result = QP_TEST_RESULT_PASS;

	static const char* s_faceNames[] = { "-X", "+X", "-Y", "+Y", "-Z", "+Z" };
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_faceNames) == tcu::CUBEFACE_LAST);

	// Special comparison for level 0.
	for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
	{
		const tcu::CubeFace	face		= tcu::CubeFace(faceNdx);
		const tcu::Vec4		threshold	= select(precision.colorThreshold, tcu::Vec4(1.0f), precision.colorMask);
		const bool			level0Ok	= tcu::floatThresholdCompare(log,
																	 ("Level0Face" + de::toString(faceNdx)).c_str(),
																	 (string("Level 0, face ") + s_faceNames[face]).c_str(),
																	 level0Reference.getLevelFace(0, face),
																	 resultTexture.getLevelFace(0, face),
																	 threshold, tcu::COMPARE_LOG_RESULT);

		if (!level0Ok)
		{
			log << TestLog::Message << "ERROR: Level 0, face " << s_faceNames[face] << " comparison failed!" << TestLog::EndMessage;
			result = QP_TEST_RESULT_FAIL;
		}
	}

	for (int levelNdx = 1; levelNdx < resultTexture.getNumLevels(); levelNdx++)
	{
		for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
		{
			const tcu::CubeFace					face		= tcu::CubeFace(faceNdx);
			const char*							faceName	= s_faceNames[face];
			const tcu::ConstPixelBufferAccess	src			= resultTexture.getLevelFace(levelNdx-1,	face);
			const tcu::ConstPixelBufferAccess	dst			= resultTexture.getLevelFace(levelNdx,		face);
			tcu::Surface						errorMask	(dst.getWidth(), dst.getHeight());
			bool								levelOk		= false;

			// Try different comparisons in quality order.

			if (!levelOk)
			{
				const int numFailed = compareGenMipmapBilinear(dst, src, errorMask.getAccess(), precision);
				if (numFailed == 0)
					levelOk = true;
				else
					log << TestLog::Message << "WARNING: Level " << levelNdx << ", face " << faceName << " comparison to bilinear method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
			}

			if (!levelOk)
			{
				const int numFailed = compareGenMipmapBox(dst, src, errorMask.getAccess(), precision);
				if (numFailed == 0)
					levelOk = true;
				else
					log << TestLog::Message << "WARNING: Level " << levelNdx << ", face " << faceName <<" comparison to box method failed, found " << numFailed << " invalid pixels." << TestLog::EndMessage;
			}

			// At this point all high-quality methods have been used.
			if (!levelOk && result == QP_TEST_RESULT_PASS)
				result = QP_TEST_RESULT_QUALITY_WARNING;

			if (!levelOk)
			{
				const int numFailed = compareGenMipmapVeryLenient(dst, src, errorMask.getAccess(), precision);
				if (numFailed == 0)
					levelOk = true;
				else
					log << TestLog::Message << "ERROR: Level " << levelNdx << ", face " << faceName << " appears to contain " << numFailed << " completely wrong pixels, failing case!" << TestLog::EndMessage;
			}

			if (!levelOk)
				result = QP_TEST_RESULT_FAIL;

			log << TestLog::ImageSet(string("Level") + de::toString(levelNdx) + "Face" + de::toString(faceNdx), string("Level ") + de::toString(levelNdx) + ", face " + string(faceName) + " result")
				<< TestLog::Image("Result", "Result", dst);

			if (!levelOk)
				log << TestLog::Image("ErrorMask", "Error mask", errorMask);

			log << TestLog::EndImageSet;
		}
	}

	return result;
}

// Logging utilities.

std::ostream& operator<< (std::ostream& str, const LogGradientFmt& fmt)
{
	return str << "(R: " << fmt.valueMin->x() << " -> " << fmt.valueMax->x() << ", "
			   <<  "G: " << fmt.valueMin->y() << " -> " << fmt.valueMax->y() << ", "
			   <<  "B: " << fmt.valueMin->z() << " -> " << fmt.valueMax->z() << ", "
			   <<  "A: " << fmt.valueMin->w() << " -> " << fmt.valueMax->w() << ")";
}

} // TextureTestUtil
} // gls
} // deqp