Add triangle edge guardband for Tex2D lookup diff

Previously 3D and cube textures allowed pixels along the seam of
triangles to use data from either triangle. This change copies that
allowance to the 2D texture computeTextureLookupDiff. The projection
values chosen for the mipmap.2d.projected group make the data
discontinous over the edge, making it sensitive to tie-breaker
resolution. This change resolves the issue.

Affects: framework

Test: Executed mipmap.2d.projected group with failing result images
Bug: 63335787
Change-Id: Ibaed590fb5e1bc9eb837c9027ddadcea2081f629
diff --git a/framework/opengl/gluTextureTestUtil.cpp b/framework/opengl/gluTextureTestUtil.cpp
index a010b4d..70f5529 100644
--- a/framework/opengl/gluTextureTestUtil.cpp
+++ b/framework/opengl/gluTextureTestUtil.cpp
@@ -1356,6 +1356,8 @@
 
 	const tcu::Vec2								lodBias				((sampleParams.flags & ReferenceParams::USE_BIAS) ? sampleParams.bias : 0.0f);
 
+	const float									posEps				= 1.0f / float(1<<MIN_SUBPIXEL_BITS);
+
 	int											numFailed			= 0;
 
 	const tcu::Vec2 lodOffsets[] =
@@ -1387,42 +1389,56 @@
 				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 bool		tri0	= (wx-posEps)/dstW + (wy-posEps)/dstH <= 1.0f;
+				const bool		tri1	= (wx+posEps)/dstW + (wy+posEps)/dstH >= 1.0f;
 
-				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();
+				bool			isOk	= false;
 
-				tcu::Vec2		lodBounds	= tcu::computeLodBoundsFromDerivates(coordDx.x(), coordDx.y(), coordDy.x(), coordDy.y(), lodPrec);
+				DE_ASSERT(tri0 || tri1);
 
-				// Compute lod bounds across lodOffsets range.
-				for (int lodOffsNdx = 0; lodOffsNdx < DE_LENGTH_OF_ARRAY(lodOffsets); lodOffsNdx++)
+				// 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		wxo		= triWx + lodOffsets[lodOffsNdx].x();
-					const float		wyo		= triWy + lodOffsets[lodOffsNdx].y();
-					const float		nxo		= wxo/dstW;
-					const float		nyo		= wyo/dstH;
+					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	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);
+					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();
 
-					lodBounds.x() = de::min(lodBounds.x(), lodO.x());
-					lodBounds.y() = de::max(lodBounds.y(), lodO.y());
+					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	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);
+					if (tcu::isLookupResultValid(src, sampleParams.sampler, lookupPrec, coord, clampedLod, resPix))
+					{
+						isOk = true;
+						break;
+					}
 				}
 
-				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);