Improve texture border color handling.

- Apply border color only to active channels.
- Clamp border color to format range as specified in GL.
- Support int and uint border colors.
- Convert border color of sRGB formats to linear.
- Support border color in texture compare verifier.

Change-Id: Id191c605e61aa513a1aa65c3009dabda72c81163
diff --git a/framework/common/tcuTexCompareVerifier.cpp b/framework/common/tcuTexCompareVerifier.cpp
index 9806c70..9f10295 100644
--- a/framework/common/tcuTexCompareVerifier.cpp
+++ b/framework/common/tcuTexCompareVerifier.cpp
@@ -44,11 +44,6 @@
 }
 #endif // DE_DEBUG
 
-static inline float lookupDepth (const ConstPixelBufferAccess& access, int i, int j, int k = 0)
-{
-	return access.getPixDepth(i, j, k);
-}
-
 struct CmpResultSet
 {
 	bool	isTrue;
@@ -131,6 +126,27 @@
 		   (resultSet.isFalse	&& de::inRange(0.0f, minR, maxR));
 }
 
+static inline bool coordsInBounds (const ConstPixelBufferAccess& access, int x, int y, int z)
+{
+	return de::inBounds(x, 0, access.getWidth()) && de::inBounds(y, 0, access.getHeight()) && de::inBounds(z, 0, access.getDepth());
+}
+
+static float lookupDepth (const tcu::ConstPixelBufferAccess& access, const Sampler& sampler, int i, int j, int k)
+{
+	if (coordsInBounds(access, i, j, k))
+		return access.getPixDepth(i, j, k);
+	else
+		return sampleTextureBorder<float>(access.getFormat(), sampler).x();
+}
+
+// lookup depth value at a point that is guaranteed to not sample border such as cube map faces.
+static float lookupDepthNoBorder (const tcu::ConstPixelBufferAccess& access, const Sampler& sampler, int i, int j, int k = 0)
+{
+	DE_UNREF(sampler);
+	DE_ASSERT(coordsInBounds(access, i, j, k));
+	return access.getPixDepth(i, j, k);
+}
+
 // Values are in order (0,0), (1,0), (0,1), (1,1)
 static float bilinearInterpolate (const Vec4& values, const float x, const float y)
 {
@@ -543,7 +559,7 @@
 		{
 			const int			x		= wrap(sampler.wrapS, i, level.getWidth());
 			const int			y		= wrap(sampler.wrapT, j, level.getHeight());
-			const float			depth	= lookupDepth(level, x, y, coordZ);
+			const float			depth	= lookupDepth(level, sampler, x, y, coordZ);
 			const CmpResultSet	resSet	= execCompare(sampler.compare, depth, cmpReference, prec.referenceBits, isFixedPointDepth);
 
 			if (isResultInSet(resSet, result, prec.resultBits))
@@ -593,10 +609,10 @@
 			const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
 			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);
 
-			const Vec4	depths	(lookupDepth(level, x0, y0, coordZ),
-								 lookupDepth(level, x1, y0, coordZ),
-								 lookupDepth(level, x0, y1, coordZ),
-								 lookupDepth(level, x1, y1, coordZ));
+			const Vec4	depths	(lookupDepth(level, sampler, x0, y0, coordZ),
+								 lookupDepth(level, sampler, x1, y0, coordZ),
+								 lookupDepth(level, sampler, x0, y1, coordZ),
+								 lookupDepth(level, sampler, x1, y1, coordZ));
 
 			if (isBilinearCompareValid(sampler.compare, prec, depths, Vec2(minA, maxA), Vec2(minB, maxB), cmpReference, result, isFixedPointDepth))
 				return true;
@@ -657,13 +673,13 @@
 	{
 		for (int i0 = minI0; i0 <= maxI0; i0++)
 		{
-			const float	depth0	= lookupDepth(level0, wrap(sampler.wrapS, i0, w0), wrap(sampler.wrapT, j0, h0), coordZ);
+			const float	depth0	= lookupDepth(level0, sampler, wrap(sampler.wrapS, i0, w0), wrap(sampler.wrapT, j0, h0), coordZ);
 
 			for (int j1 = minJ1; j1 <= maxJ1; j1++)
 			{
 				for (int i1 = minI1; i1 <= maxI1; i1++)
 				{
-					const float	depth1	= lookupDepth(level1, wrap(sampler.wrapS, i1, w1), wrap(sampler.wrapT, j1, h1), coordZ);
+					const float	depth1	= lookupDepth(level1, sampler, wrap(sampler.wrapS, i1, w1), wrap(sampler.wrapT, j1, h1), coordZ);
 
 					if (isLinearCompareValid(sampler.compare, prec, Vec2(depth0, depth1), fBounds, cmpReference, result, isFixedPointDepth))
 						return true;
@@ -726,10 +742,10 @@
 				const int	y0		= wrap(sampler.wrapT, j0  , h0);
 				const int	y1		= wrap(sampler.wrapT, j0+1, h0);
 
-				depths0[0] = lookupDepth(level0, x0, y0, coordZ);
-				depths0[1] = lookupDepth(level0, x1, y0, coordZ);
-				depths0[2] = lookupDepth(level0, x0, y1, coordZ);
-				depths0[3] = lookupDepth(level0, x1, y1, coordZ);
+				depths0[0] = lookupDepth(level0, sampler, x0, y0, coordZ);
+				depths0[1] = lookupDepth(level0, sampler, x1, y0, coordZ);
+				depths0[2] = lookupDepth(level0, sampler, x0, y1, coordZ);
+				depths0[3] = lookupDepth(level0, sampler, x1, y1, coordZ);
 			}
 
 			for (int j1 = minJ1; j1 <= maxJ1; j1++)
@@ -748,10 +764,10 @@
 						const int	y0		= wrap(sampler.wrapT, j1  , h1);
 						const int	y1		= wrap(sampler.wrapT, j1+1, h1);
 
-						depths1[0] = lookupDepth(level1, x0, y0, coordZ);
-						depths1[1] = lookupDepth(level1, x1, y0, coordZ);
-						depths1[2] = lookupDepth(level1, x0, y1, coordZ);
-						depths1[3] = lookupDepth(level1, x1, y1, coordZ);
+						depths1[0] = lookupDepth(level1, sampler, x0, y0, coordZ);
+						depths1[1] = lookupDepth(level1, sampler, x1, y0, coordZ);
+						depths1[2] = lookupDepth(level1, sampler, x0, y1, coordZ);
+						depths1[3] = lookupDepth(level1, sampler, x1, y1, coordZ);
 					}
 
 					if (isTrilinearCompareValid(sampler.compare, prec, depths0, depths1,
@@ -913,10 +929,10 @@
 				if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
 					return true;
 
-				depths0[0] = lookupDepth(faces0[c00.face], c00.s, c00.t);
-				depths0[1] = lookupDepth(faces0[c10.face], c10.s, c10.t);
-				depths0[2] = lookupDepth(faces0[c01.face], c01.s, c01.t);
-				depths0[3] = lookupDepth(faces0[c11.face], c11.s, c11.t);
+				depths0[0] = lookupDepthNoBorder(faces0[c00.face], sampler, c00.s, c00.t);
+				depths0[1] = lookupDepthNoBorder(faces0[c10.face], sampler, c10.s, c10.t);
+				depths0[2] = lookupDepthNoBorder(faces0[c01.face], sampler, c01.s, c01.t);
+				depths0[3] = lookupDepthNoBorder(faces0[c11.face], sampler, c11.s, c11.t);
 			}
 
 			for (int j1 = minJ1; j1 <= maxJ1; j1++)
@@ -938,10 +954,10 @@
 						if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
 							return true;
 
-						depths1[0] = lookupDepth(faces1[c00.face], c00.s, c00.t);
-						depths1[1] = lookupDepth(faces1[c10.face], c10.s, c10.t);
-						depths1[2] = lookupDepth(faces1[c01.face], c01.s, c01.t);
-						depths1[3] = lookupDepth(faces1[c11.face], c11.s, c11.t);
+						depths1[0] = lookupDepthNoBorder(faces1[c00.face], sampler, c00.s, c00.t);
+						depths1[1] = lookupDepthNoBorder(faces1[c10.face], sampler, c10.s, c10.t);
+						depths1[2] = lookupDepthNoBorder(faces1[c01.face], sampler, c01.s, c01.t);
+						depths1[3] = lookupDepthNoBorder(faces1[c11.face], sampler, c11.s, c11.t);
 					}
 
 
@@ -1029,10 +1045,10 @@
 			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);
 
 			Vec4 depths;
-			depths[0] = lookupDepth(faces[c00.face], c00.s, c00.t);
-			depths[1] = lookupDepth(faces[c10.face], c10.s, c10.t);
-			depths[2] = lookupDepth(faces[c01.face], c01.s, c01.t);
-			depths[3] = lookupDepth(faces[c11.face], c11.s, c11.t);
+			depths[0] = lookupDepthNoBorder(faces[c00.face], sampler, c00.s, c00.t);
+			depths[1] = lookupDepthNoBorder(faces[c10.face], sampler, c10.s, c10.t);
+			depths[2] = lookupDepthNoBorder(faces[c01.face], sampler, c01.s, c01.t);
+			depths[3] = lookupDepthNoBorder(faces[c11.face], sampler, c11.s, c11.t);
 
 			if (isBilinearCompareValid(sampler.compare, prec, depths, Vec2(minA, maxA), Vec2(minB, maxB), cmpReference, result, isFixedPointDepth))
 				return true;
@@ -1246,7 +1262,7 @@
 				// offNdx-th coordinate offset and then wrapped.
 				const int			x		= wrap(sampler.wrapS, i+offsets[offNdx].x(), w);
 				const int			y		= wrap(sampler.wrapT, j+offsets[offNdx].y(), h);
-				const float			depth	= lookupDepth(texture, x, y, coordZ);
+				const float			depth	= lookupDepth(texture, sampler, x, y, coordZ);
 				const CmpResultSet	resSet	= execCompare(sampler.compare, depth, cmpReference, prec.referenceBits, isFixedPointDepth);
 
 				if (!isResultInSet(resSet, result[offNdx], prec.resultBits))
@@ -1344,7 +1360,7 @@
 				if (c.face == CUBEFACE_LAST)
 					return true;
 
-				const float			depth	= lookupDepth(faces[c.face], c.s, c.t);
+				const float			depth	= lookupDepthNoBorder(faces[c.face], sampler, c.s, c.t);
 				const CmpResultSet	resSet	= execCompare(sampler.compare, depth, cmpReference, prec.referenceBits, isFixedPointDepth);
 
 				if (!isResultInSet(resSet, result[offNdx], prec.resultBits))
diff --git a/framework/common/tcuTexLookupVerifier.cpp b/framework/common/tcuTexLookupVerifier.cpp
index 89aa55b..e7d7ccb 100644
--- a/framework/common/tcuTexLookupVerifier.cpp
+++ b/framework/common/tcuTexLookupVerifier.cpp
@@ -58,20 +58,21 @@
 	if (coordsInBounds(access, i, j, k))
 		return access.getPixelT<ScalarType>(i, j, k);
 	else
-		return sampler.borderColor.cast<ScalarType>();
+		return sampleTextureBorder<ScalarType>(access.getFormat(), sampler);
 }
 
 template<>
 inline Vector<float, 4> lookup (const ConstPixelBufferAccess& access, const Sampler& sampler, int i, int j, int k)
 {
 	// Specialization for float lookups: sRGB conversion is performed as specified in format.
+	Vec4 p;
+
 	if (coordsInBounds(access, i, j, k))
-	{
-		const Vec4 p = access.getPixel(i, j, k);
-		return isSRGB(access.getFormat()) ? sRGBToLinear(p) : p;
-	}
+		p = access.getPixel(i, j, k);
 	else
-		return sampler.borderColor;
+		p = sampleTextureBorder<float>(access.getFormat(), sampler);
+
+	return isSRGB(access.getFormat()) ? sRGBToLinear(p) : p;
 }
 
 static inline bool isColorValid (const LookupPrecision& prec, const Vec4& ref, const Vec4& result)
diff --git a/framework/common/tcuTexture.cpp b/framework/common/tcuTexture.cpp
index eafb5c1..713cc4c 100644
--- a/framework/common/tcuTexture.cpp
+++ b/framework/common/tcuTexture.cpp
@@ -1235,11 +1235,33 @@
 	return isSRGB(access.getFormat()) ? sRGBToLinear(p) : p;
 }
 
-// Border texel lookup
+// Border texel lookup with color conversion.
 static inline Vec4 lookupBorder (const tcu::TextureFormat& format, const tcu::Sampler& sampler)
 {
-	DE_UNREF(format);
-	return sampler.borderColor;
+	// "lookup" for a combined format does not make sense, disallow
+	DE_ASSERT(!isCombinedDepthStencilType(format.type));
+
+	const tcu::TextureChannelClass	channelClass 			= tcu::getTextureChannelClass(format.type);
+	const bool						isFloat					= channelClass == tcu::TEXTURECHANNELCLASS_FLOATING_POINT;
+	const bool						isFixed					= channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT ||
+															  channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
+	const bool						isPureInteger			= channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER;
+	const bool						isPureUnsignedInteger	= channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER;
+
+	if (isFloat || isFixed)
+	{
+		const Vec4 p = sampleTextureBorder<float>(format, sampler);
+		return isSRGB(format) ? sRGBToLinear(p) : p;
+	}
+	else if (isPureInteger)
+		return sampleTextureBorder<deInt32>(format, sampler).cast<float>();
+	else if (isPureUnsignedInteger)
+		return sampleTextureBorder<deUint32>(format, sampler).cast<float>();
+	else
+	{
+		DE_ASSERT(false);
+		return Vec4(-1.0);
+	}
 }
 
 static inline float execCompare (const tcu::Vec4& color, Sampler::CompareMode compare, int chanNdx, float ref_, bool isFixedPoint)
@@ -1947,9 +1969,9 @@
 	DE_ASSERT(src.getFormat().order == TextureFormat::D || src.getFormat().order == TextureFormat::DS);
 	DE_ASSERT(sampler.compareChannel == 0);
 
-	const bool						isFixedPoint	= isFixedPointDepthTextureFormat(src.getFormat());
-	const Vec4						gathered		= fetchGatherArray2DOffsets(src, sampler, s, t, depth, 0 /* component 0: depth */, offsets);
-	Vec4							result;
+	const bool	isFixedPoint	= isFixedPointDepthTextureFormat(src.getFormat());
+	const Vec4	gathered		= fetchGatherArray2DOffsets(src, sampler, s, t, depth, 0 /* component 0: depth */, offsets);
+	Vec4		result;
 
 	for (int i = 0; i < 4; i++)
 		result[i] = execCompare(gathered, sampler.compare, i, ref, isFixedPoint);
diff --git a/framework/common/tcuTexture.hpp b/framework/common/tcuTexture.hpp
index 307fb2d..fde4919 100644
--- a/framework/common/tcuTexture.hpp
+++ b/framework/common/tcuTexture.hpp
@@ -25,6 +25,7 @@
 
 #include "tcuDefs.hpp"
 #include "tcuVector.hpp"
+#include "rrGenericVector.hpp"
 #include "deArrayBuffer.hpp"
 
 #include <vector>
@@ -221,8 +222,12 @@
 	CompareMode			compare;
 	int					compareChannel;
 
-	// Border color
-	Vec4			borderColor;
+	// Border color.
+	// \note It is setter's responsibility to guarantee that the values are representable
+	//       in sampled texture's internal format.
+	// \note It is setter's responsibility to guarantee that the format is compatible with the
+	//       sampled texture's internal format. Otherwise results are undefined.
+	rr::GenericVec4		borderColor;
 
 	// Seamless cube map filtering
 	bool				seamlessCubeMap;
@@ -267,7 +272,7 @@
 		, normalizedCoords	(true)
 		, compare			(COMPAREMODE_NONE)
 		, compareChannel	(0)
-		, borderColor		(0.0f, 0.0f, 0.0f, 0.0f)
+		, borderColor		(Vec4(0.0f, 0.0f, 0.0f, 0.0f))
 		, seamlessCubeMap	(false)
 		, depthStencilMode	(MODE_DEPTH)
 	{
diff --git a/framework/common/tcuTextureUtil.cpp b/framework/common/tcuTextureUtil.cpp
index 58e2809..7edd3b8 100644
--- a/framework/common/tcuTextureUtil.cpp
+++ b/framework/common/tcuTextureUtil.cpp
@@ -577,9 +577,9 @@
 	DE_ASSERT(access.getHeight() == 1);
 	for (int x = 0; x < access.getWidth(); x++)
 	{
-		float s	= ((float)x + 0.5f) / (float)access.getWidth();
+		float s = ((float)x + 0.5f) / (float)access.getWidth();
 
-		float r	= linearInterpolate(s, minVal.x(), maxVal.x());
+		float r = linearInterpolate(s, minVal.x(), maxVal.x());
 		float g = linearInterpolate(s, minVal.y(), maxVal.y());
 		float b = linearInterpolate(s, minVal.z(), maxVal.z());
 		float a = linearInterpolate(s, minVal.w(), maxVal.w());
@@ -594,10 +594,10 @@
 	{
 		for (int x = 0; x < access.getWidth(); x++)
 		{
-			float s	= ((float)x + 0.5f) / (float)access.getWidth();
-			float t	= ((float)y + 0.5f) / (float)access.getHeight();
+			float s = ((float)x + 0.5f) / (float)access.getWidth();
+			float t = ((float)y + 0.5f) / (float)access.getHeight();
 
-			float r	= linearInterpolate((      s  +       t) *0.5f, minVal.x(), maxVal.x());
+			float r = linearInterpolate((      s  +       t) *0.5f, minVal.x(), maxVal.x());
 			float g = linearInterpolate((      s  + (1.0f-t))*0.5f, minVal.y(), maxVal.y());
 			float b = linearInterpolate(((1.0f-s) +       t) *0.5f, minVal.z(), maxVal.z());
 			float a = linearInterpolate(((1.0f-s) + (1.0f-t))*0.5f, minVal.w(), maxVal.w());
@@ -1149,4 +1149,223 @@
 	return getEffectiveTView(src, storage, sampler);
 }
 
+//! Returns the effective swizzle of a border color. The effective swizzle is the
+//! equal to first writing an RGBA color with a write swizzle and then reading
+//! it back using a read swizzle, i.e. BorderSwizzle(c) == readSwizzle(writeSwizzle(C))
+static const TextureSwizzle& getBorderColorReadSwizzle (TextureFormat::ChannelOrder order)
+{
+	// make sure to update these tables when channel orders are updated
+	DE_STATIC_ASSERT(TextureFormat::CHANNELORDER_LAST == 18);
+
+	static const TextureSwizzle INV		= {{ TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ONE	}};
+	static const TextureSwizzle R		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ONE	}};
+	static const TextureSwizzle A		= {{ TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_3	}};
+	static const TextureSwizzle I		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_0	}};
+	static const TextureSwizzle L		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_ONE	}};
+	static const TextureSwizzle LA		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_3	}};
+	static const TextureSwizzle RG		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_1,		TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ONE	}};
+	static const TextureSwizzle RA		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_3	}};
+	static const TextureSwizzle RGB		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_1,		TextureSwizzle::CHANNEL_2,		TextureSwizzle::CHANNEL_ONE	}};
+	static const TextureSwizzle RGBA	= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_1,		TextureSwizzle::CHANNEL_2,		TextureSwizzle::CHANNEL_3	}};
+	static const TextureSwizzle D		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ONE	}};
+	static const TextureSwizzle S		= {{ TextureSwizzle::CHANNEL_0,		TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ZERO,	TextureSwizzle::CHANNEL_ONE	}};
+
+	const TextureSwizzle* swizzle;
+
+	switch (order)
+	{
+		case TextureFormat::R:			swizzle = &R;		break;
+		case TextureFormat::A:			swizzle = &A;		break;
+		case TextureFormat::I:			swizzle = &I;		break;
+		case TextureFormat::L:			swizzle = &L;		break;
+		case TextureFormat::LA:			swizzle = &LA;		break;
+		case TextureFormat::RG:			swizzle = &RG;		break;
+		case TextureFormat::RA:			swizzle = &RA;		break;
+		case TextureFormat::RGB:		swizzle = &RGB;		break;
+		case TextureFormat::RGBA:		swizzle = &RGBA;	break;
+		case TextureFormat::ARGB:		swizzle = &RGBA;	break;
+		case TextureFormat::BGRA:		swizzle = &RGBA;	break;
+		case TextureFormat::sR:			swizzle = &R;		break;
+		case TextureFormat::sRG:		swizzle = &RG;		break;
+		case TextureFormat::sRGB:		swizzle = &RGB;		break;
+		case TextureFormat::sRGBA:		swizzle = &RGBA;	break;
+		case TextureFormat::D:			swizzle = &D;		break;
+		case TextureFormat::S:			swizzle = &S;		break;
+
+		case TextureFormat::DS:
+			DE_ASSERT(false); // combined depth-stencil border color?
+			swizzle = &INV;
+			break;
+
+		default:
+			DE_ASSERT(false);
+			swizzle = &INV;
+			break;
+	}
+
+#ifdef DE_DEBUG
+
+	{
+		// check that BorderSwizzle(c) == readSwizzle(writeSwizzle(C))
+		const TextureSwizzle& readSwizzle	= getChannelReadSwizzle(order);
+		const TextureSwizzle& writeSwizzle	= getChannelWriteSwizzle(order);
+
+		for (int ndx = 0; ndx < 4; ++ndx)
+		{
+			TextureSwizzle::Channel writeRead = readSwizzle.components[ndx];
+			if (deInRange32(writeRead, TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_3) == DE_TRUE)
+				writeRead = writeSwizzle.components[(int)writeRead];
+			DE_ASSERT(writeRead == swizzle->components[ndx]);
+		}
+	}
+
+#endif
+
+	return *swizzle;
+}
+
+static tcu::UVec4 getNBitUnsignedIntegerVec4MaxValue (const tcu::IVec4& numBits)
+{
+	return tcu::UVec4((numBits[0] > 0) ? (deUintMaxValue32(numBits[0])) : (0),
+					  (numBits[1] > 0) ? (deUintMaxValue32(numBits[1])) : (0),
+					  (numBits[2] > 0) ? (deUintMaxValue32(numBits[2])) : (0),
+					  (numBits[3] > 0) ? (deUintMaxValue32(numBits[3])) : (0));
+}
+
+static tcu::IVec4 getNBitSignedIntegerVec4MaxValue (const tcu::IVec4& numBits)
+{
+	return tcu::IVec4((numBits[0] > 0) ? (deIntMaxValue32(numBits[0])) : (0),
+					  (numBits[1] > 0) ? (deIntMaxValue32(numBits[1])) : (0),
+					  (numBits[2] > 0) ? (deIntMaxValue32(numBits[2])) : (0),
+					  (numBits[3] > 0) ? (deIntMaxValue32(numBits[3])) : (0));
+}
+
+static tcu::IVec4 getNBitSignedIntegerVec4MinValue (const tcu::IVec4& numBits)
+{
+	return tcu::IVec4((numBits[0] > 0) ? (deIntMinValue32(numBits[0])) : (0),
+					  (numBits[1] > 0) ? (deIntMinValue32(numBits[1])) : (0),
+					  (numBits[2] > 0) ? (deIntMinValue32(numBits[2])) : (0),
+					  (numBits[3] > 0) ? (deIntMinValue32(numBits[3])) : (0));
+}
+
+static tcu::Vec4 getTextureBorderColorFloat (const TextureFormat& format, const Sampler& sampler)
+{
+	const tcu::TextureChannelClass	channelClass 	= getTextureChannelClass(format.type);
+	const TextureSwizzle::Channel*	channelMap		= getBorderColorReadSwizzle(format.order).components;
+	const bool						isFloat			= channelClass == tcu::TEXTURECHANNELCLASS_FLOATING_POINT;
+	const bool						isSigned		= channelClass != tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
+	const float						valueMin		= (isSigned) ? (-1.0f) : (0.0f);
+	const float						valueMax		= 1.0f;
+	Vec4							result;
+
+	DE_ASSERT(channelClass == tcu::TEXTURECHANNELCLASS_FLOATING_POINT ||
+			  channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT ||
+			  channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT);
+
+	for (int c = 0; c < 4; c++)
+	{
+		const TextureSwizzle::Channel map = channelMap[c];
+		if (map == TextureSwizzle::CHANNEL_ZERO)
+			result[c] = 0.0f;
+		else if (map == TextureSwizzle::CHANNEL_ONE)
+			result[c] = 1.0f;
+		else if (isFloat)
+		{
+			// floating point values are not clamped
+			result[c] = sampler.borderColor.getAccess<float>()[(int)map];
+		}
+		else
+		{
+			// fixed point values are clamped to a representable range
+			result[c] = de::clamp(sampler.borderColor.getAccess<float>()[(int)map], valueMin, valueMax);
+		}
+	}
+
+	return isSRGB(format) ? sRGBToLinear(result) : result;
+}
+
+static tcu::IVec4 getTextureBorderColorInt (const TextureFormat& format, const Sampler& sampler)
+{
+	const tcu::TextureChannelClass	channelClass 	= getTextureChannelClass(format.type);
+	const TextureSwizzle::Channel*	channelMap		= getBorderColorReadSwizzle(format.order).components;
+	const IVec4						channelBits		= getChannelBitDepth(format.type);
+	const IVec4						valueMin		= getNBitSignedIntegerVec4MinValue(channelBits);
+	const IVec4						valueMax		= getNBitSignedIntegerVec4MaxValue(channelBits);
+	IVec4							result;
+
+	DE_ASSERT(channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER);
+
+	for (int c = 0; c < 4; c++)
+	{
+		const TextureSwizzle::Channel map = channelMap[c];
+		if (map == TextureSwizzle::CHANNEL_ZERO)
+			result[c] = 0;
+		else if (map == TextureSwizzle::CHANNEL_ONE)
+			result[c] = 1;
+		else
+		{
+			// integer values are clamped to a representable range
+			result[c] = de::clamp(sampler.borderColor.getAccess<deInt32>()[(int)map], valueMin[(int)map], valueMax[(int)map]);
+		}
+	}
+
+	return result;
+}
+
+static tcu::UVec4 getTextureBorderColorUint (const TextureFormat& format, const Sampler& sampler)
+{
+	const tcu::TextureChannelClass	channelClass 	= getTextureChannelClass(format.type);
+	const TextureSwizzle::Channel*	channelMap		= getBorderColorReadSwizzle(format.order).components;
+	const IVec4						channelBits		= getChannelBitDepth(format.type);
+	const UVec4						valueMax		= getNBitUnsignedIntegerVec4MaxValue(channelBits);
+	UVec4							result;
+
+	DE_ASSERT(channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER);
+
+	for (int c = 0; c < 4; c++)
+	{
+		const TextureSwizzle::Channel map = channelMap[c];
+		if (map == TextureSwizzle::CHANNEL_ZERO)
+			result[c] = 0;
+		else if (map == TextureSwizzle::CHANNEL_ONE)
+			result[c] = 1;
+		else
+		{
+			// integer values are clamped to a representable range
+			result[c] = de::min(sampler.borderColor.getAccess<deUint32>()[(int)map], valueMax[(int)map]);
+		}
+	}
+
+	return result;
+}
+
+template <typename ScalarType>
+tcu::Vector<ScalarType, 4> sampleTextureBorder (const TextureFormat& format, const Sampler& sampler)
+{
+	const tcu::TextureChannelClass channelClass = getTextureChannelClass(format.type);
+
+	switch (channelClass)
+	{
+		case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
+		case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
+			return getTextureBorderColorFloat(format, sampler).cast<ScalarType>();
+
+		case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
+			return getTextureBorderColorInt(format, sampler).cast<ScalarType>();
+
+		case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
+			return getTextureBorderColorUint(format, sampler).cast<ScalarType>();
+
+		default:
+			DE_ASSERT(false);
+			return tcu::Vector<ScalarType, 4>();
+	}
+}
+
+// instantiation
+template tcu::Vector<float, 4>		sampleTextureBorder (const TextureFormat& format, const Sampler& sampler);
+template tcu::Vector<deInt32, 4>	sampleTextureBorder (const TextureFormat& format, const Sampler& sampler);
+template tcu::Vector<deUint32, 4>	sampleTextureBorder (const TextureFormat& format, const Sampler& sampler);
+
 } // tcu
diff --git a/framework/common/tcuTextureUtil.hpp b/framework/common/tcuTextureUtil.hpp
index f1870ec..c51e303 100644
--- a/framework/common/tcuTextureUtil.hpp
+++ b/framework/common/tcuTextureUtil.hpp
@@ -153,6 +153,9 @@
 tcu::TextureCubeView		getEffectiveTextureView					(const tcu::TextureCubeView&		src, std::vector<tcu::ConstPixelBufferAccess>& storage, const tcu::Sampler& sampler);
 tcu::TextureCubeArrayView	getEffectiveTextureView					(const tcu::TextureCubeArrayView&	src, std::vector<tcu::ConstPixelBufferAccess>& storage, const tcu::Sampler& sampler);
 
+template <typename ScalarType>
+tcu::Vector<ScalarType, 4>	sampleTextureBorder						(const TextureFormat& format, const Sampler& sampler);
+
 } // tcu
 
 #endif // _TCUTEXTUREUTIL_HPP