C++程序  |  2413行  |  104.53 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 rasterization test utils.
 *//*--------------------------------------------------------------------*/

#include "glsRasterizationTestUtil.hpp"
#include "tcuVector.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuFloat.hpp"
#include "deMath.h"

#include "rrRasterizer.hpp"

#include <limits>

namespace deqp
{
namespace gls
{
namespace RasterizationTestUtil
{
namespace
{

bool lineLineIntersect (const tcu::Vector<deInt64, 2>& line0Beg, const tcu::Vector<deInt64, 2>& line0End, const tcu::Vector<deInt64, 2>& line1Beg, const tcu::Vector<deInt64, 2>& line1End)
{
	typedef tcu::Vector<deInt64, 2> I64Vec2;

	// Lines do not intersect if the other line's endpoints are on the same side
	// otherwise, the do intersect

	// Test line 0
	{
		const I64Vec2 line			= line0End - line0Beg;
		const I64Vec2 v0			= line1Beg - line0Beg;
		const I64Vec2 v1			= line1End - line0Beg;
		const deInt64 crossProduct0	= (line.x() * v0.y() - line.y() * v0.x());
		const deInt64 crossProduct1	= (line.x() * v1.y() - line.y() * v1.x());

		// check signs
		if ((crossProduct0 < 0 && crossProduct1 < 0) ||
			(crossProduct0 > 0 && crossProduct1 > 0))
			return false;
	}

	// Test line 1
	{
		const I64Vec2 line			= line1End - line1Beg;
		const I64Vec2 v0			= line0Beg - line1Beg;
		const I64Vec2 v1			= line0End - line1Beg;
		const deInt64 crossProduct0	= (line.x() * v0.y() - line.y() * v0.x());
		const deInt64 crossProduct1	= (line.x() * v1.y() - line.y() * v1.x());

		// check signs
		if ((crossProduct0 < 0 && crossProduct1 < 0) ||
			(crossProduct0 > 0 && crossProduct1 > 0))
			return false;
	}

	return true;
}

bool isTriangleClockwise (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2)
{
	const tcu::Vec2	u				(p1.x() / p1.w() - p0.x() / p0.w(), p1.y() / p1.w() - p0.y() / p0.w());
	const tcu::Vec2	v				(p2.x() / p2.w() - p0.x() / p0.w(), p2.y() / p2.w() - p0.y() / p0.w());
	const float		crossProduct	= (u.x() * v.y() - u.y() * v.x());

	return crossProduct > 0.0f;
}

bool compareColors (const tcu::RGBA& colorA, const tcu::RGBA& colorB, int redBits, int greenBits, int blueBits)
{
	const int thresholdRed		= 1 << (8 - redBits);
	const int thresholdGreen	= 1 << (8 - greenBits);
	const int thresholdBlue		= 1 << (8 - blueBits);

	return	deAbs32(colorA.getRed()   - colorB.getRed())   <= thresholdRed   &&
			deAbs32(colorA.getGreen() - colorB.getGreen()) <= thresholdGreen &&
			deAbs32(colorA.getBlue()  - colorB.getBlue())  <= thresholdBlue;
}

bool pixelNearLineSegment (const tcu::IVec2& pixel, const tcu::Vec2& p0, const tcu::Vec2& p1)
{
	const tcu::Vec2 pixelCenterPosition = tcu::Vec2((float)pixel.x() + 0.5f, (float)pixel.y() + 0.5f);

	// "Near" = Distance from the line to the pixel is less than 2 * pixel_max_radius. (pixel_max_radius = sqrt(2) / 2)
	const float maxPixelDistance		= 1.414f;
	const float maxPixelDistanceSquared	= 2.0f;

	// Near the line
	{
		const tcu::Vec2	line			= p1                  - p0;
		const tcu::Vec2	v				= pixelCenterPosition - p0;
		const float		crossProduct	= (line.x() * v.y() - line.y() * v.x());

		// distance to line: (line x v) / |line|
		//     |(line x v) / |line|| > maxPixelDistance
		// ==> (line x v)^2 / |line|^2 > maxPixelDistance^2
		// ==> (line x v)^2 > maxPixelDistance^2 * |line|^2

		if (crossProduct * crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(line))
			return false;
	}

	// Between the endpoints
	{
		// distance from line endpoint 1 to pixel is less than line length + maxPixelDistance
		const float maxDistance = tcu::length(p1 - p0) + maxPixelDistance;

		if (tcu::length(pixelCenterPosition - p0) > maxDistance)
			return false;
		if (tcu::length(pixelCenterPosition - p1) > maxDistance)
			return false;
	}

	return true;
}

bool pixelOnlyOnASharedEdge (const tcu::IVec2& pixel, const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize)
{
	if (triangle.sharedEdge[0] || triangle.sharedEdge[1] || triangle.sharedEdge[2])
	{
		const tcu::Vec2 triangleNormalizedDeviceSpace[3] =
		{
			tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()),
			tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()),
			tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()),
		};
		const tcu::Vec2 triangleScreenSpace[3] =
		{
			(triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
			(triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
			(triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
		};

		const bool pixelOnEdge0 = pixelNearLineSegment(pixel, triangleScreenSpace[0], triangleScreenSpace[1]);
		const bool pixelOnEdge1 = pixelNearLineSegment(pixel, triangleScreenSpace[1], triangleScreenSpace[2]);
		const bool pixelOnEdge2 = pixelNearLineSegment(pixel, triangleScreenSpace[2], triangleScreenSpace[0]);

		// If the pixel is on a multiple edges return false

		if (pixelOnEdge0 && !pixelOnEdge1 && !pixelOnEdge2)
			return triangle.sharedEdge[0];
		if (!pixelOnEdge0 && pixelOnEdge1 && !pixelOnEdge2)
			return triangle.sharedEdge[1];
		if (!pixelOnEdge0 && !pixelOnEdge1 && pixelOnEdge2)
			return triangle.sharedEdge[2];
	}

	return false;
}

float triangleArea (const tcu::Vec2& s0, const tcu::Vec2& s1, const tcu::Vec2& s2)
{
	const tcu::Vec2	u				(s1.x() - s0.x(), s1.y() - s0.y());
	const tcu::Vec2	v				(s2.x() - s0.x(), s2.y() - s0.y());
	const float		crossProduct	= (u.x() * v.y() - u.y() * v.x());

	return crossProduct / 2.0f;
}

tcu::IVec4 getTriangleAABB (const TriangleSceneSpec::SceneTriangle& triangle, const tcu::IVec2& viewportSize)
{
	const tcu::Vec2 normalizedDeviceSpace[3] =
	{
		tcu::Vec2(triangle.positions[0].x() / triangle.positions[0].w(), triangle.positions[0].y() / triangle.positions[0].w()),
		tcu::Vec2(triangle.positions[1].x() / triangle.positions[1].w(), triangle.positions[1].y() / triangle.positions[1].w()),
		tcu::Vec2(triangle.positions[2].x() / triangle.positions[2].w(), triangle.positions[2].y() / triangle.positions[2].w()),
	};
	const tcu::Vec2 screenSpace[3] =
	{
		(normalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
		(normalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
		(normalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
	};

	tcu::IVec4 aabb;

	aabb.x() = (int)deFloatFloor(de::min(de::min(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
	aabb.y() = (int)deFloatFloor(de::min(de::min(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));
	aabb.z() = (int)deFloatCeil (de::max(de::max(screenSpace[0].x(), screenSpace[1].x()), screenSpace[2].x()));
	aabb.w() = (int)deFloatCeil (de::max(de::max(screenSpace[0].y(), screenSpace[1].y()), screenSpace[2].y()));

	return aabb;
}

float getExponentEpsilonFromULP (int valueExponent, deUint32 ulp)
{
	DE_ASSERT(ulp < (1u<<10));

	// assume mediump precision, using ulp as ulps in a 10 bit mantissa
	return tcu::Float32::construct(+1, valueExponent, (1u<<23) + (ulp << (23 - 10))).asFloat() - tcu::Float32::construct(+1, valueExponent, (1u<<23)).asFloat();
}

float getValueEpsilonFromULP (float value, deUint32 ulp)
{
	DE_ASSERT(value != std::numeric_limits<float>::infinity() && value != -std::numeric_limits<float>::infinity());

	const int exponent = tcu::Float32(value).exponent();
	return getExponentEpsilonFromULP(exponent, ulp);
}

float getMaxValueWithinError (float value, deUint32 ulp)
{
	if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
		return value;

	return value + getValueEpsilonFromULP(value, ulp);
}

float getMinValueWithinError (float value, deUint32 ulp)
{
	if (value == std::numeric_limits<float>::infinity() || value == -std::numeric_limits<float>::infinity())
		return value;

	return value - getValueEpsilonFromULP(value, ulp);
}

float getMinFlushToZero (float value)
{
	// flush to zero if that decreases the value
	// assume mediump precision
	if (value > 0.0f && value < tcu::Float32::construct(+1, -14, 1u<<23).asFloat())
		return 0.0f;
	return value;
}

float getMaxFlushToZero (float value)
{
	// flush to zero if that increases the value
	// assume mediump precision
	if (value < 0.0f && value > tcu::Float32::construct(-1, -14, 1u<<23).asFloat())
		return 0.0f;
	return value;
}

tcu::IVec3 convertRGB8ToNativeFormat (const tcu::RGBA& color, const RasterizationArguments& args)
{
	tcu::IVec3 pixelNativeColor;

	for (int channelNdx = 0; channelNdx < 3; ++channelNdx)
	{
		const int channelBitCount	= (channelNdx == 0) ? (args.redBits) : (channelNdx == 1) ? (args.greenBits) : (args.blueBits);
		const int channelPixelValue	= (channelNdx == 0) ? (color.getRed()) : (channelNdx == 1) ? (color.getGreen()) : (color.getBlue());

		if (channelBitCount <= 8)
			pixelNativeColor[channelNdx] = channelPixelValue >> (8 - channelBitCount);
		else if (channelBitCount == 8)
			pixelNativeColor[channelNdx] = channelPixelValue;
		else
		{
			// just in case someone comes up with 8+ bits framebuffers pixel formats. But as
			// we can only read in rgba8, we have to guess the trailing bits. Guessing 0.
			pixelNativeColor[channelNdx] = channelPixelValue << (channelBitCount - 8);
		}
	}

	return pixelNativeColor;
}

/*--------------------------------------------------------------------*//*!
 * Returns the maximum value of x / y, where x c [minDividend, maxDividend]
 * and y c [minDivisor, maxDivisor]
 *//*--------------------------------------------------------------------*/
float maximalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor)
{
	DE_ASSERT(minDividend <= maxDividend);
	DE_ASSERT(minDivisor <= maxDivisor);

	// special cases
	if (minDividend == 0.0f && maxDividend == 0.0f)
		return 0.0f;
	if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
		return std::numeric_limits<float>::infinity();

	return de::max(de::max(minDividend / minDivisor, minDividend / maxDivisor), de::max(maxDividend / minDivisor, maxDividend / maxDivisor));
}

/*--------------------------------------------------------------------*//*!
 * Returns the minimum value of x / y, where x c [minDividend, maxDividend]
 * and y c [minDivisor, maxDivisor]
 *//*--------------------------------------------------------------------*/
float minimalRangeDivision (float minDividend, float maxDividend, float minDivisor, float maxDivisor)
{
	DE_ASSERT(minDividend <= maxDividend);
	DE_ASSERT(minDivisor <= maxDivisor);

	// special cases
	if (minDividend == 0.0f && maxDividend == 0.0f)
		return 0.0f;
	if (minDivisor <= 0.0f && maxDivisor >= 0.0f)
		return -std::numeric_limits<float>::infinity();

	return de::min(de::min(minDividend / minDivisor, minDividend / maxDivisor), de::min(maxDividend / minDivisor, maxDividend / maxDivisor));
}

static bool isLineXMajor (const tcu::Vec2& lineScreenSpaceP0, const tcu::Vec2& lineScreenSpaceP1)
{
	return de::abs(lineScreenSpaceP1.x() - lineScreenSpaceP0.x()) >= de::abs(lineScreenSpaceP1.y() - lineScreenSpaceP0.y());
}

static bool isPackedSSLineXMajor (const tcu::Vec4& packedLine)
{
	const tcu::Vec2 lineScreenSpaceP0 = packedLine.swizzle(0, 1);
	const tcu::Vec2 lineScreenSpaceP1 = packedLine.swizzle(2, 3);

	return isLineXMajor(lineScreenSpaceP0, lineScreenSpaceP1);
}

struct InterpolationRange
{
	tcu::Vec3 max;
	tcu::Vec3 min;
};

struct LineInterpolationRange
{
	tcu::Vec2 max;
	tcu::Vec2 min;
};

InterpolationRange calcTriangleInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::Vec2& ndpixel)
{
	const int roundError		= 1;
	const int barycentricError	= 3;
	const int divError			= 8;

	const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
	const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
	const tcu::Vec2 nd2 = p2.swizzle(0, 1) / p2.w();

	const float ka = triangleArea(ndpixel, nd1, nd2);
	const float kb = triangleArea(ndpixel, nd2, nd0);
	const float kc = triangleArea(ndpixel, nd0, nd1);

	const float kaMax = getMaxFlushToZero(getMaxValueWithinError(ka, barycentricError));
	const float kbMax = getMaxFlushToZero(getMaxValueWithinError(kb, barycentricError));
	const float kcMax = getMaxFlushToZero(getMaxValueWithinError(kc, barycentricError));
	const float kaMin = getMinFlushToZero(getMinValueWithinError(ka, barycentricError));
	const float kbMin = getMinFlushToZero(getMinValueWithinError(kb, barycentricError));
	const float kcMin = getMinFlushToZero(getMinValueWithinError(kc, barycentricError));
	DE_ASSERT(kaMin <= kaMax);
	DE_ASSERT(kbMin <= kbMax);
	DE_ASSERT(kcMin <= kcMax);

	// calculate weights: vec3(ka / p0.w, kb / p1.w, kc / p2.w) / (ka / p0.w + kb / p1.w + kc / p2.w)
	const float maxPreDivisionValues[3] =
	{
		getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kaMax / p0.w()), divError)),
		getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kbMax / p1.w()), divError)),
		getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(kcMax / p2.w()), divError)),
	};
	const float minPreDivisionValues[3] =
	{
		getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kaMin / p0.w()), divError)),
		getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kbMin / p1.w()), divError)),
		getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(kcMin / p2.w()), divError)),
	};
	DE_ASSERT(minPreDivisionValues[0] <= maxPreDivisionValues[0]);
	DE_ASSERT(minPreDivisionValues[1] <= maxPreDivisionValues[1]);
	DE_ASSERT(minPreDivisionValues[2] <= maxPreDivisionValues[2]);

	const float maxDivisor = getMaxFlushToZero(getMaxValueWithinError(maxPreDivisionValues[0] + maxPreDivisionValues[1] + maxPreDivisionValues[2], 2*roundError));
	const float minDivisor = getMinFlushToZero(getMinValueWithinError(minPreDivisionValues[0] + minPreDivisionValues[1] + minPreDivisionValues[2], 2*roundError));
	DE_ASSERT(minDivisor <= maxDivisor);

	InterpolationRange returnValue;

	returnValue.max.x() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError));
	returnValue.max.y() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError));
	returnValue.max.z() = getMaxFlushToZero(getMaxValueWithinError(getMaxFlushToZero(maximalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError));
	returnValue.min.x() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[0], maxPreDivisionValues[0], minDivisor, maxDivisor)), divError));
	returnValue.min.y() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[1], maxPreDivisionValues[1], minDivisor, maxDivisor)), divError));
	returnValue.min.z() = getMinFlushToZero(getMinValueWithinError(getMinFlushToZero(minimalRangeDivision(minPreDivisionValues[2], maxPreDivisionValues[2], minDivisor, maxDivisor)), divError));

	DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
	DE_ASSERT(returnValue.min.y() <= returnValue.max.y());
	DE_ASSERT(returnValue.min.z() <= returnValue.max.z());

	return returnValue;
}

LineInterpolationRange calcLineInterpolationWeights (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr)
{
	const int roundError	= 1;
	const int divError		= 3;

	// calc weights:
	//			(1-t) / wa					t / wb
	//		-------------------	,	-------------------
	//		(1-t) / wa + t / wb		(1-t) / wa + t / wb

	// Allow 1 ULP
	const float		dividend	= tcu::dot(pr - pa, pb - pa);
	const float		dividendMax	= getMaxValueWithinError(dividend, 1);
	const float		dividendMin	= getMinValueWithinError(dividend, 1);
	DE_ASSERT(dividendMin <= dividendMax);

	// Assuming lengthSquared will not be implemented as sqrt(x)^2, allow 1 ULP
	const float		divisor		= tcu::lengthSquared(pb - pa);
	const float		divisorMax	= getMaxValueWithinError(divisor, 1);
	const float		divisorMin	= getMinValueWithinError(divisor, 1);
	DE_ASSERT(divisorMin <= divisorMax);

	// Allow 3 ULP precision for division
	const float		tMax		= getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
	const float		tMin		= getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
	DE_ASSERT(tMin <= tMax);

	const float		perspectiveTMax			= getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError);
	const float		perspectiveTMin			= getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError);
	DE_ASSERT(perspectiveTMin <= perspectiveTMax);

	const float		perspectiveInvTMax		= getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
	const float		perspectiveInvTMin		= getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
	DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);

	const float		perspectiveDivisorMax	= getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
	const float		perspectiveDivisorMin	= getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
	DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);

	LineInterpolationRange returnValue;
	returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
	returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
	returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
	returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);

	DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
	DE_ASSERT(returnValue.min.y() <= returnValue.max.y());

	return returnValue;
}

LineInterpolationRange calcLineInterpolationWeightsAxisProjected (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr)
{
	const int	roundError		= 1;
	const int	divError		= 3;
	const bool	isXMajor		= isLineXMajor(pa, pb);
	const int	majorAxisNdx	= (isXMajor) ? (0) : (1);

	// calc weights:
	//			(1-t) / wa					t / wb
	//		-------------------	,	-------------------
	//		(1-t) / wa + t / wb		(1-t) / wa + t / wb

	// Use axis projected (inaccurate) method, i.e. for X-major lines:
	//     (xd - xa) * (xb - xa)      xd - xa
	// t = ---------------------  ==  -------
	//       ( xb - xa ) ^ 2          xb - xa

	// Allow 1 ULP
	const float		dividend	= (pr[majorAxisNdx] - pa[majorAxisNdx]);
	const float		dividendMax	= getMaxValueWithinError(dividend, 1);
	const float		dividendMin	= getMinValueWithinError(dividend, 1);
	DE_ASSERT(dividendMin <= dividendMax);

	// Allow 1 ULP
	const float		divisor		= (pb[majorAxisNdx] - pa[majorAxisNdx]);
	const float		divisorMax	= getMaxValueWithinError(divisor, 1);
	const float		divisorMin	= getMinValueWithinError(divisor, 1);
	DE_ASSERT(divisorMin <= divisorMax);

	// Allow 3 ULP precision for division
	const float		tMax		= getMaxValueWithinError(maximalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
	const float		tMin		= getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
	DE_ASSERT(tMin <= tMax);

	const float		perspectiveTMax			= getMaxValueWithinError(maximalRangeDivision(tMin, tMax, wb, wb), divError);
	const float		perspectiveTMin			= getMinValueWithinError(minimalRangeDivision(tMin, tMax, wb, wb), divError);
	DE_ASSERT(perspectiveTMin <= perspectiveTMax);

	const float		perspectiveInvTMax		= getMaxValueWithinError(maximalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
	const float		perspectiveInvTMin		= getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), wa, wa), divError);
	DE_ASSERT(perspectiveInvTMin <= perspectiveInvTMax);

	const float		perspectiveDivisorMax	= getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
	const float		perspectiveDivisorMin	= getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
	DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);

	LineInterpolationRange returnValue;
	returnValue.max.x() = getMaxValueWithinError(maximalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
	returnValue.max.y() = getMaxValueWithinError(maximalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
	returnValue.min.x() = getMinValueWithinError(minimalRangeDivision(perspectiveInvTMin,	perspectiveInvTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);
	returnValue.min.y() = getMinValueWithinError(minimalRangeDivision(perspectiveTMin,		perspectiveTMax,	perspectiveDivisorMin, perspectiveDivisorMax), divError);

	DE_ASSERT(returnValue.min.x() <= returnValue.max.x());
	DE_ASSERT(returnValue.min.y() <= returnValue.max.y());

	return returnValue;
}

template <typename WeightEquation>
LineInterpolationRange calcSingleSampleLineInterpolationRangeWithWeightEquation (const tcu::Vec2&	pa,
																				 float				wa,
																				 const tcu::Vec2&	pb,
																				 float				wb,
																				 const tcu::IVec2&	pixel,
																				 int				subpixelBits,
																				 WeightEquation		weightEquation)
{
	// allow interpolation weights anywhere in the central subpixels
	const float testSquareSize = (2.0f / (float)(1UL << subpixelBits));
	const float testSquarePos  = (0.5f - testSquareSize / 2);

	const tcu::Vec2 corners[4] =
	{
		tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f,				(float)pixel.y() + testSquarePos + 0.0f),
		tcu::Vec2((float)pixel.x() + testSquarePos + 0.0f,				(float)pixel.y() + testSquarePos + testSquareSize),
		tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize,	(float)pixel.y() + testSquarePos + testSquareSize),
		tcu::Vec2((float)pixel.x() + testSquarePos + testSquareSize,	(float)pixel.y() + testSquarePos + 0.0f),
	};

	// calculate interpolation as a line
	const LineInterpolationRange weights[4] =
	{
		weightEquation(pa, wa, pb, wb, corners[0]),
		weightEquation(pa, wa, pb, wb, corners[1]),
		weightEquation(pa, wa, pb, wb, corners[2]),
		weightEquation(pa, wa, pb, wb, corners[3]),
	};

	const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
	const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));

	LineInterpolationRange result;
	result.min = minWeights;
	result.max = maxWeights;
	return result;
}

LineInterpolationRange calcSingleSampleLineInterpolationRange (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::IVec2& pixel, int subpixelBits)
{
	return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits, calcLineInterpolationWeights);
}

LineInterpolationRange calcSingleSampleLineInterpolationRangeAxisProjected (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::IVec2& pixel, int subpixelBits)
{
	return calcSingleSampleLineInterpolationRangeWithWeightEquation(pa, wa, pb, wb, pixel, subpixelBits, calcLineInterpolationWeightsAxisProjected);
}

struct TriangleInterpolator
{
	const TriangleSceneSpec& scene;

	TriangleInterpolator (const TriangleSceneSpec& scene_)
		: scene(scene_)
	{
	}

	InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const
	{
		// allow anywhere in the pixel area in multisample
		// allow only in the center subpixels (4 subpixels) in singlesample
		const float testSquareSize = (multisample) ? (1.0f) : (2.0f / (float)(1UL << subpixelBits));
		const float testSquarePos  = (multisample) ? (0.0f) : (0.5f - testSquareSize / 2);
		const tcu::Vec2 corners[4] =
		{
			tcu::Vec2(((float)pixel.x() + testSquarePos + 0.0f)           / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + 0.0f          ) / (float)viewportSize.y() * 2.0f - 1.0f),
			tcu::Vec2(((float)pixel.x() + testSquarePos + 0.0f)           / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + testSquareSize) / (float)viewportSize.y() * 2.0f - 1.0f),
			tcu::Vec2(((float)pixel.x() + testSquarePos + testSquareSize) / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + testSquareSize) / (float)viewportSize.y() * 2.0f - 1.0f),
			tcu::Vec2(((float)pixel.x() + testSquarePos + testSquareSize) / (float)viewportSize.x() * 2.0f - 1.0f, ((float)pixel.y() + testSquarePos + 0.0f          ) / (float)viewportSize.y() * 2.0f - 1.0f),
		};
		const InterpolationRange weights[4] =
		{
			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[0]),
			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[1]),
			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[2]),
			calcTriangleInterpolationWeights(scene.triangles[primitiveNdx].positions[0], scene.triangles[primitiveNdx].positions[1], scene.triangles[primitiveNdx].positions[2], corners[3]),
		};

		InterpolationRange result;
		result.min = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
		result.max = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));
		return result;
	}
};

/*--------------------------------------------------------------------*//*!
 * Used only by verifyMultisampleLineGroupInterpolation to calculate
 * correct line interpolations for the triangulated lines.
 *//*--------------------------------------------------------------------*/
struct MultisampleLineInterpolator
{
	const LineSceneSpec& scene;

	MultisampleLineInterpolator (const LineSceneSpec& scene_)
		: scene(scene_)
	{
	}

	InterpolationRange interpolate (int primitiveNdx, const tcu::IVec2 pixel, const tcu::IVec2 viewportSize, bool multisample, int subpixelBits) const
	{
		DE_ASSERT(multisample);
		DE_UNREF(multisample);
		DE_UNREF(subpixelBits);

		// in triangulation, one line emits two triangles
		const int		lineNdx		= primitiveNdx / 2;

		// allow interpolation weights anywhere in the pixel
		const tcu::Vec2 corners[4] =
		{
			tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 0.0f),
			tcu::Vec2((float)pixel.x() + 0.0f, (float)pixel.y() + 1.0f),
			tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 1.0f),
			tcu::Vec2((float)pixel.x() + 1.0f, (float)pixel.y() + 0.0f),
		};

		const float		wa = scene.lines[lineNdx].positions[0].w();
		const float		wb = scene.lines[lineNdx].positions[1].w();
		const tcu::Vec2	pa = tcu::Vec2((scene.lines[lineNdx].positions[0].x() / wa + 1.0f) * 0.5f * (float)viewportSize.x(),
									   (scene.lines[lineNdx].positions[0].y() / wa + 1.0f) * 0.5f * (float)viewportSize.y());
		const tcu::Vec2	pb = tcu::Vec2((scene.lines[lineNdx].positions[1].x() / wb + 1.0f) * 0.5f * (float)viewportSize.x(),
									   (scene.lines[lineNdx].positions[1].y() / wb + 1.0f) * 0.5f * (float)viewportSize.y());

		// calculate interpolation as a line
		const LineInterpolationRange weights[4] =
		{
			calcLineInterpolationWeights(pa, wa, pb, wb, corners[0]),
			calcLineInterpolationWeights(pa, wa, pb, wb, corners[1]),
			calcLineInterpolationWeights(pa, wa, pb, wb, corners[2]),
			calcLineInterpolationWeights(pa, wa, pb, wb, corners[3]),
		};

		const tcu::Vec2 minWeights = tcu::min(tcu::min(weights[0].min, weights[1].min), tcu::min(weights[2].min, weights[3].min));
		const tcu::Vec2 maxWeights = tcu::max(tcu::max(weights[0].max, weights[1].max), tcu::max(weights[2].max, weights[3].max));

		// convert to three-component form. For all triangles, the vertex 0 is always emitted by the line starting point, and vertex 2 by the ending point
		InterpolationRange result;
		result.min = tcu::Vec3(minWeights.x(), 0.0f, minWeights.y());
		result.max = tcu::Vec3(maxWeights.x(), 0.0f, maxWeights.y());
		return result;
	}
};

template <typename Interpolator>
bool verifyTriangleGroupInterpolationWithInterpolator (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, const Interpolator& interpolator)
{
	const tcu::RGBA		invalidPixelColor	= tcu::RGBA(255, 0, 0, 255);
	const bool			multisampled		= (args.numSamples != 0);
	const tcu::IVec2	viewportSize		= tcu::IVec2(surface.getWidth(), surface.getHeight());
	const int			errorFloodThreshold	= 4;
	int					errorCount			= 0;
	int					invalidPixels		= 0;
	int					subPixelBits		= args.subpixelBits;
	tcu::Surface		errorMask			(surface.getWidth(), surface.getHeight());

	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));

	// log format

	log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
	if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
		log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage;

	// subpixel bits in in a valid range?

	if (subPixelBits < 0)
	{
		log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage;
		subPixelBits = 0;
	}
	else if (subPixelBits > 16)
	{
		// At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
		log << tcu::TestLog::Message << "Subpixel count is greater than 16 (" << subPixelBits << "). Checking results using less strict 16 bit requirements. This may produce false positives." << tcu::TestLog::EndMessage;
		subPixelBits = 16;
	}

	// check pixels

	for (int y = 0; y < surface.getHeight(); ++y)
	for (int x = 0; x < surface.getWidth();  ++x)
	{
		const tcu::RGBA		color				= surface.getPixel(x, y);
		bool				stackBottomFound	= false;
		int					stackSize			= 0;
		tcu::Vec4			colorStackMin;
		tcu::Vec4			colorStackMax;

		// Iterate triangle coverage front to back, find the stack of pontentially contributing fragments
		for (int triNdx = (int)scene.triangles.size() - 1; triNdx >= 0; --triNdx)
		{
			const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0],
																	scene.triangles[triNdx].positions[1],
																	scene.triangles[triNdx].positions[2],
																	tcu::IVec2(x, y),
																	viewportSize,
																	subPixelBits,
																	multisampled);

			if (coverage == COVERAGE_FULL || coverage == COVERAGE_PARTIAL)
			{
				// potentially contributes to the result fragment's value
				const InterpolationRange weights = interpolator.interpolate(triNdx, tcu::IVec2(x, y), viewportSize, multisampled, subPixelBits);

				const tcu::Vec4 fragmentColorMax =	de::clamp(weights.max.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
													de::clamp(weights.max.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
													de::clamp(weights.max.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];
				const tcu::Vec4 fragmentColorMin =	de::clamp(weights.min.x(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[0] +
													de::clamp(weights.min.y(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[1] +
													de::clamp(weights.min.z(), 0.0f, 1.0f) * scene.triangles[triNdx].colors[2];

				if (stackSize++ == 0)
				{
					// first triangle, set the values properly
					colorStackMin = fragmentColorMin;
					colorStackMax = fragmentColorMax;
				}
				else
				{
					// contributing triangle
					colorStackMin = tcu::min(colorStackMin, fragmentColorMin);
					colorStackMax = tcu::max(colorStackMax, fragmentColorMax);
				}

				if (coverage == COVERAGE_FULL)
				{
					// loop terminates, this is the bottommost fragment
					stackBottomFound = true;
					break;
				}
			}
		}

		// Partial coverage == background may be visible
		if (stackSize != 0 && !stackBottomFound)
		{
			stackSize++;
			colorStackMin = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
		}

		// Is the result image color in the valid range.
		if (stackSize == 0)
		{
			// No coverage, allow only background (black, value=0)
			const tcu::IVec3	pixelNativeColor	= convertRGB8ToNativeFormat(color, args);
			const int			threshold			= 1;

			if (pixelNativeColor.x() > threshold ||
				pixelNativeColor.y() > threshold ||
				pixelNativeColor.z() > threshold)
			{
				++errorCount;

				// don't fill the logs with too much data
				if (errorCount < errorFloodThreshold)
				{
					log << tcu::TestLog::Message
						<< "Found an invalid pixel at (" << x << "," << y << ")\n"
						<< "\tPixel color:\t\t" << color << "\n"
						<< "\tExpected background color.\n"
						<< tcu::TestLog::EndMessage;
				}

				++invalidPixels;
				errorMask.setPixel(x, y, invalidPixelColor);
			}
		}
		else
		{
			DE_ASSERT(stackSize);

			// Each additional step in the stack may cause conversion error of 1 bit due to undefined rounding direction
			const int			thresholdRed	= stackSize - 1;
			const int			thresholdGreen	= stackSize - 1;
			const int			thresholdBlue	= stackSize - 1;

			const tcu::Vec3		valueRangeMin	= tcu::Vec3(colorStackMin.xyz());
			const tcu::Vec3		valueRangeMax	= tcu::Vec3(colorStackMax.xyz());

			const tcu::IVec3	formatLimit		((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
			const tcu::Vec3		colorMinF		(de::clamp(valueRangeMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
												 de::clamp(valueRangeMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
												 de::clamp(valueRangeMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
			const tcu::Vec3		colorMaxF		(de::clamp(valueRangeMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
												 de::clamp(valueRangeMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
												 de::clamp(valueRangeMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
			const tcu::IVec3	colorMin		((int)deFloatFloor(colorMinF.x()),
												 (int)deFloatFloor(colorMinF.y()),
												 (int)deFloatFloor(colorMinF.z()));
			const tcu::IVec3	colorMax		((int)deFloatCeil (colorMaxF.x()),
												 (int)deFloatCeil (colorMaxF.y()),
												 (int)deFloatCeil (colorMaxF.z()));

			// Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
			const tcu::IVec3 pixelNativeColor = convertRGB8ToNativeFormat(color, args);

			// Validity check
			if (pixelNativeColor.x() < colorMin.x() - thresholdRed   ||
				pixelNativeColor.y() < colorMin.y() - thresholdGreen ||
				pixelNativeColor.z() < colorMin.z() - thresholdBlue  ||
				pixelNativeColor.x() > colorMax.x() + thresholdRed   ||
				pixelNativeColor.y() > colorMax.y() + thresholdGreen ||
				pixelNativeColor.z() > colorMax.z() + thresholdBlue)
			{
				++errorCount;

				// don't fill the logs with too much data
				if (errorCount <= errorFloodThreshold)
				{
					log << tcu::TestLog::Message
						<< "Found an invalid pixel at (" << x << "," << y << ")\n"
						<< "\tPixel color:\t\t" << color << "\n"
						<< "\tNative color:\t\t" << pixelNativeColor << "\n"
						<< "\tAllowed error:\t\t" << tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue) << "\n"
						<< "\tReference native color min: " << tcu::clamp(colorMin - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n"
						<< "\tReference native color max: " << tcu::clamp(colorMax + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue), tcu::IVec3(0,0,0), formatLimit) << "\n"
						<< "\tReference native float min: " << tcu::clamp(colorMinF - tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(), tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
						<< "\tReference native float max: " << tcu::clamp(colorMaxF + tcu::IVec3(thresholdRed, thresholdGreen, thresholdBlue).cast<float>(), tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
						<< "\tFmin:\t" << tcu::clamp(valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
						<< "\tFmax:\t" << tcu::clamp(valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
						<< tcu::TestLog::EndMessage;
				}

				++invalidPixels;
				errorMask.setPixel(x, y, invalidPixelColor);
			}
		}
	}

	// don't just hide failures
	if (errorCount > errorFloodThreshold)
		log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage;

	// report result
	if (invalidPixels)
	{
		log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result",			surface)
			<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
			<< tcu::TestLog::EndImageSet;

		return false;
	}
	else
	{
		log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result", surface)
			<< tcu::TestLog::EndImageSet;

		return true;
	}
}

bool verifyMultisampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	// Multisampled line == 2 triangles

	const tcu::Vec2		viewportSize	= tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
	const float			halfLineWidth	= scene.lineWidth * 0.5f;
	TriangleSceneSpec	triangleScene;

	triangleScene.triangles.resize(2 * scene.lines.size());
	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
	{
		// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
		const tcu::Vec2 lineNormalizedDeviceSpace[2] =
		{
			tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()),
			tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()),
		};
		const tcu::Vec2 lineScreenSpace[2] =
		{
			(lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
			(lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
		};

		const tcu::Vec2 lineDir			= tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
		const tcu::Vec2 lineNormalDir	= tcu::Vec2(lineDir.y(), -lineDir.x());

		const tcu::Vec2 lineQuadScreenSpace[4] =
		{
			lineScreenSpace[0] + lineNormalDir * halfLineWidth,
			lineScreenSpace[0] - lineNormalDir * halfLineWidth,
			lineScreenSpace[1] - lineNormalDir * halfLineWidth,
			lineScreenSpace[1] + lineNormalDir * halfLineWidth,
		};
		const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
		{
			lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
		};

		triangleScene.triangles[lineNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false;
		triangleScene.triangles[lineNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false;
		triangleScene.triangles[lineNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true;

		triangleScene.triangles[lineNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true;
		triangleScene.triangles[lineNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false;
		triangleScene.triangles[lineNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);	triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false;
	}

	return verifyTriangleGroupRasterization(surface, triangleScene, args, log);
}

bool verifyMultisampleLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	// Multisampled line == 2 triangles

	const tcu::Vec2		viewportSize	= tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
	const float			halfLineWidth	= scene.lineWidth * 0.5f;
	TriangleSceneSpec	triangleScene;

	triangleScene.triangles.resize(2 * scene.lines.size());
	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
	{
		// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
		const tcu::Vec2 lineNormalizedDeviceSpace[2] =
		{
			tcu::Vec2(scene.lines[lineNdx].positions[0].x() / scene.lines[lineNdx].positions[0].w(), scene.lines[lineNdx].positions[0].y() / scene.lines[lineNdx].positions[0].w()),
			tcu::Vec2(scene.lines[lineNdx].positions[1].x() / scene.lines[lineNdx].positions[1].w(), scene.lines[lineNdx].positions[1].y() / scene.lines[lineNdx].positions[1].w()),
		};
		const tcu::Vec2 lineScreenSpace[2] =
		{
			(lineNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
			(lineNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize,
		};

		const tcu::Vec2 lineDir			= tcu::normalize(lineScreenSpace[1] - lineScreenSpace[0]);
		const tcu::Vec2 lineNormalDir	= tcu::Vec2(lineDir.y(), -lineDir.x());

		const tcu::Vec2 lineQuadScreenSpace[4] =
		{
			lineScreenSpace[0] + lineNormalDir * halfLineWidth,
			lineScreenSpace[0] - lineNormalDir * halfLineWidth,
			lineScreenSpace[1] - lineNormalDir * halfLineWidth,
			lineScreenSpace[1] + lineNormalDir * halfLineWidth,
		};
		const tcu::Vec2 lineQuadNormalizedDeviceSpace[4] =
		{
			lineQuadScreenSpace[0] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			lineQuadScreenSpace[1] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			lineQuadScreenSpace[2] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			lineQuadScreenSpace[3] / viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
		};

		triangleScene.triangles[lineNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
		triangleScene.triangles[lineNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);
		triangleScene.triangles[lineNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);

		triangleScene.triangles[lineNdx*2 + 0].sharedEdge[0] = false;
		triangleScene.triangles[lineNdx*2 + 0].sharedEdge[1] = false;
		triangleScene.triangles[lineNdx*2 + 0].sharedEdge[2] = true;

		triangleScene.triangles[lineNdx*2 + 0].colors[0] = scene.lines[lineNdx].colors[0];
		triangleScene.triangles[lineNdx*2 + 0].colors[1] = scene.lines[lineNdx].colors[0];
		triangleScene.triangles[lineNdx*2 + 0].colors[2] = scene.lines[lineNdx].colors[1];

		triangleScene.triangles[lineNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);
		triangleScene.triangles[lineNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);
		triangleScene.triangles[lineNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);

		triangleScene.triangles[lineNdx*2 + 1].sharedEdge[0] = true;
		triangleScene.triangles[lineNdx*2 + 1].sharedEdge[1] = false;
		triangleScene.triangles[lineNdx*2 + 1].sharedEdge[2] = false;

		triangleScene.triangles[lineNdx*2 + 1].colors[0] = scene.lines[lineNdx].colors[0];
		triangleScene.triangles[lineNdx*2 + 1].colors[1] = scene.lines[lineNdx].colors[1];
		triangleScene.triangles[lineNdx*2 + 1].colors[2] = scene.lines[lineNdx].colors[1];
	}

	return verifyTriangleGroupInterpolationWithInterpolator(surface, triangleScene, args, log, MultisampleLineInterpolator(scene));
}

bool verifyMultisamplePointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	// Multisampled point == 2 triangles

	const tcu::Vec2		viewportSize	= tcu::Vec2((float)surface.getWidth(), (float)surface.getHeight());
	TriangleSceneSpec	triangleScene;

	triangleScene.triangles.resize(2 * scene.points.size());
	for (int pointNdx = 0; pointNdx < (int)scene.points.size(); ++pointNdx)
	{
		// Transform to screen space, add pixel offsets, convert back to normalized device space, and test as triangles
		const tcu::Vec2	pointNormalizedDeviceSpace			= tcu::Vec2(scene.points[pointNdx].position.x() / scene.points[pointNdx].position.w(), scene.points[pointNdx].position.y() / scene.points[pointNdx].position.w());
		const tcu::Vec2	pointScreenSpace					= (pointNormalizedDeviceSpace + tcu::Vec2(1.0f, 1.0f)) * 0.5f * viewportSize;
		const float		offset								= scene.points[pointNdx].pointSize * 0.5f;
		const tcu::Vec2	lineQuadNormalizedDeviceSpace[4]	=
		{
			(pointScreenSpace + tcu::Vec2(-offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			(pointScreenSpace + tcu::Vec2(-offset,  offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			(pointScreenSpace + tcu::Vec2( offset,  offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
			(pointScreenSpace + tcu::Vec2( offset, -offset))/ viewportSize * 2.0f - tcu::Vec2(1.0f, 1.0f),
		};

		triangleScene.triangles[pointNdx*2 + 0].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 0].sharedEdge[0] = false;
		triangleScene.triangles[pointNdx*2 + 0].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[1].x(), lineQuadNormalizedDeviceSpace[1].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 0].sharedEdge[1] = false;
		triangleScene.triangles[pointNdx*2 + 0].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 0].sharedEdge[2] = true;

		triangleScene.triangles[pointNdx*2 + 1].positions[0] = tcu::Vec4(lineQuadNormalizedDeviceSpace[0].x(), lineQuadNormalizedDeviceSpace[0].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 1].sharedEdge[0] = true;
		triangleScene.triangles[pointNdx*2 + 1].positions[1] = tcu::Vec4(lineQuadNormalizedDeviceSpace[2].x(), lineQuadNormalizedDeviceSpace[2].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 1].sharedEdge[1] = false;
		triangleScene.triangles[pointNdx*2 + 1].positions[2] = tcu::Vec4(lineQuadNormalizedDeviceSpace[3].x(), lineQuadNormalizedDeviceSpace[3].y(), 0.0f, 1.0f);	triangleScene.triangles[pointNdx*2 + 1].sharedEdge[2] = false;
	}

	return verifyTriangleGroupRasterization(surface, triangleScene, args, log);
}

void genScreenSpaceLines (std::vector<tcu::Vec4>& screenspaceLines, const std::vector<LineSceneSpec::SceneLine>& lines, const tcu::IVec2& viewportSize)
{
	DE_ASSERT(screenspaceLines.size() == lines.size());

	for (int lineNdx = 0; lineNdx < (int)lines.size(); ++lineNdx)
	{
		const tcu::Vec2 lineNormalizedDeviceSpace[2] =
		{
			tcu::Vec2(lines[lineNdx].positions[0].x() / lines[lineNdx].positions[0].w(), lines[lineNdx].positions[0].y() / lines[lineNdx].positions[0].w()),
			tcu::Vec2(lines[lineNdx].positions[1].x() / lines[lineNdx].positions[1].w(), lines[lineNdx].positions[1].y() / lines[lineNdx].positions[1].w()),
		};
		const tcu::Vec4 lineScreenSpace[2] =
		{
			tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)viewportSize.x(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)viewportSize.y(), 0.0f, 1.0f),
			tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)viewportSize.x(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)viewportSize.y(), 0.0f, 1.0f),
		};

		screenspaceLines[lineNdx] = tcu::Vec4(lineScreenSpace[0].x(), lineScreenSpace[0].y(), lineScreenSpace[1].x(), lineScreenSpace[1].y());
	}
}

bool verifySinglesampleLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases
	DE_ASSERT(scene.lines.size() < 255); // indices are stored as unsigned 8-bit ints

	bool					allOK				= true;
	bool					overdrawInReference	= false;
	int						referenceFragments	= 0;
	int						resultFragments		= 0;
	int						lineWidth			= deFloorFloatToInt32(scene.lineWidth + 0.5f);
	bool					imageShown			= false;
	std::vector<bool>		lineIsXMajor		(scene.lines.size());
	std::vector<tcu::Vec4>	screenspaceLines(scene.lines.size());

	// Reference renderer produces correct fragments using the diamond-rule. Make 2D int array, each cell contains the highest index (first index = 1) of the overlapping lines or 0 if no line intersects the pixel
	tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
	tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));

	genScreenSpaceLines(screenspaceLines, scene.lines, tcu::IVec2(surface.getWidth(), surface.getHeight()));

	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
	{
		rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight()));
		rasterizer.init(tcu::Vec4(screenspaceLines[lineNdx][0],
								  screenspaceLines[lineNdx][1],
								  0.0f,
								  1.0f),
						tcu::Vec4(screenspaceLines[lineNdx][2],
								  screenspaceLines[lineNdx][3],
								  0.0f,
								  1.0f),
						scene.lineWidth);

		// calculate majority of later use
		lineIsXMajor[lineNdx] = isPackedSSLineXMajor(screenspaceLines[lineNdx]);

		for (;;)
		{
			const int			maxPackets			= 32;
			int					numRasterized		= 0;
			rr::FragmentPacket	packets[maxPackets];

			rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized);

			for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
			{
				for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
				{
					if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx))
					{
						const tcu::IVec2 fragPos = packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2);

						// Check for overdraw
						if (!overdrawInReference)
							overdrawInReference = referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x() != 0;

						// Output pixel
						referenceLineMap.getAccess().setPixel(tcu::IVec4(lineNdx + 1, 0, 0, 0), fragPos.x(), fragPos.y());
					}
				}
			}

			if (numRasterized != maxPackets)
				break;
		}
	}

	// Requirement 1: The coordinates of a fragment produced by the algorithm may not deviate by more than one unit
	{
		tcu::Surface	errorMask			(surface.getWidth(), surface.getHeight());
		bool			missingFragments	= false;

		tcu::clear(errorMask.getAccess(), tcu::IVec4(0, 255, 0, 255));

		log << tcu::TestLog::Message << "Searching for deviating fragments." << tcu::TestLog::EndMessage;

		for (int y = 0; y < referenceLineMap.getHeight(); ++y)
		for (int x = 0; x < referenceLineMap.getWidth(); ++x)
		{
			const bool reference	= referenceLineMap.getAccess().getPixelInt(x, y).x() != 0;
			const bool result		= compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits);

			if (reference)
				++referenceFragments;
			if (result)
				++resultFragments;

			if (reference == result)
				continue;

			// Reference fragment here, matching result fragment must be nearby
			if (reference && !result)
			{
				bool foundFragment = false;

				if (x == 0 || y == 0 || x == referenceLineMap.getWidth() - 1 || y == referenceLineMap.getHeight() -1)
				{
					// image boundary, missing fragment could be over the image edge
					foundFragment = true;
				}

				// find nearby fragment
				for (int dy = -1; dy < 2 && !foundFragment; ++dy)
				for (int dx = -1; dx < 2 && !foundFragment; ++dx)
				{
					if (compareColors(surface.getPixel(x+dx, y+dy), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits))
						foundFragment = true;
				}

				if (!foundFragment)
				{
					missingFragments = true;
					errorMask.setPixel(x, y, tcu::RGBA::red());
				}
			}
		}

		if (missingFragments)
		{
			log << tcu::TestLog::Message << "Invalid deviation(s) found." << tcu::TestLog::EndMessage;
			log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
				<< tcu::TestLog::Image("Result", "Result",			surface)
				<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
				<< tcu::TestLog::EndImageSet;

			imageShown = true;
			allOK = false;
		}
		else
		{
			log << tcu::TestLog::Message << "No invalid deviations found." << tcu::TestLog::EndMessage;
		}
	}

	// Requirement 2: The total number of fragments produced by the algorithm may differ from
	//                that produced by the diamond-exit rule by no more than one.
	{
		// Check is not valid if the primitives intersect or otherwise share same fragments
		if (!overdrawInReference)
		{
			int allowedDeviation = (int)scene.lines.size() * lineWidth; // one pixel per primitive in the major direction

			log << tcu::TestLog::Message << "Verifying fragment counts:\n"
				<< "\tDiamond-exit rule: " << referenceFragments << " fragments.\n"
				<< "\tResult image: " << resultFragments << " fragments.\n"
				<< "\tAllowing deviation of " << allowedDeviation << " fragments.\n"
				<< tcu::TestLog::EndMessage;

			if (deAbs32(referenceFragments - resultFragments) > allowedDeviation)
			{
				tcu::Surface reference(surface.getWidth(), surface.getHeight());

				// show a helpful reference image
				tcu::clear(reference.getAccess(), tcu::IVec4(0, 0, 0, 255));
				for (int y = 0; y < surface.getHeight(); ++y)
				for (int x = 0; x < surface.getWidth(); ++x)
					if (referenceLineMap.getAccess().getPixelInt(x, y).x())
						reference.setPixel(x, y, tcu::RGBA::white());

				log << tcu::TestLog::Message << "Invalid fragment count in result image." << tcu::TestLog::EndMessage;
				log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
					<< tcu::TestLog::Image("Reference", "Reference",	reference)
					<< tcu::TestLog::Image("Result", "Result",			surface)
					<< tcu::TestLog::EndImageSet;

				allOK = false;
				imageShown = true;
			}
			else
			{
				log << tcu::TestLog::Message << "Fragment count is valid." << tcu::TestLog::EndMessage;
			}
		}
		else
		{
			log << tcu::TestLog::Message << "Overdraw in scene. Fragment count cannot be verified. Skipping fragment count checks." << tcu::TestLog::EndMessage;
		}
	}

	// Requirement 3: Line width must be constant
	{
		bool invalidWidthFound = false;

		log << tcu::TestLog::Message << "Verifying line widths of the x-major lines." << tcu::TestLog::EndMessage;
		for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
		{
			bool	fullyVisibleLine		= false;
			bool	previousPixelUndefined	= false;
			int		currentLine				= 0;
			int		currentWidth			= 1;

			for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
			{
				const bool	result	= compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits);
				int			lineID	= 0;

				// Which line does this fragment belong to?

				if (result)
				{
					bool multipleNearbyLines = false;

					for (int dy = -1; dy < 2; ++dy)
					for (int dx = -1; dx < 2; ++dx)
					{
						const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
						if (nearbyID)
						{
							if (lineID && lineID != nearbyID)
								multipleNearbyLines = true;
							lineID = nearbyID;
						}
					}

					if (multipleNearbyLines)
					{
						// Another line is too close, don't try to calculate width here
						previousPixelUndefined = true;
						continue;
					}
				}

				// Only line with id of lineID is nearby

				if (previousPixelUndefined)
				{
					// The line might have been overdrawn or not
					currentLine = lineID;
					currentWidth = 1;
					fullyVisibleLine = false;
					previousPixelUndefined = false;
				}
				else if (lineID == currentLine)
				{
					// Current line continues
					++currentWidth;
				}
				else if (lineID > currentLine)
				{
					// Another line was drawn over or the line ends
					currentLine = lineID;
					currentWidth = 1;
					fullyVisibleLine = true;
				}
				else
				{
					// The line ends
					if (fullyVisibleLine && !lineIsXMajor[currentLine-1])
					{
						// check width
						if (currentWidth != lineWidth)
						{
							log << tcu::TestLog::Message << "\tInvalid line width at (" << x - currentWidth << ", " << y << ") - (" << x - 1 << ", " << y << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage;
							invalidWidthFound = true;
						}
					}

					currentLine = lineID;
					currentWidth = 1;
					fullyVisibleLine = false;
				}
			}
		}

		log << tcu::TestLog::Message << "Verifying line widths of the y-major lines." << tcu::TestLog::EndMessage;
		for (int x = 1; x < referenceLineMap.getWidth() - 1; ++x)
		{
			bool	fullyVisibleLine		= false;
			bool	previousPixelUndefined	= false;
			int		currentLine				= 0;
			int		currentWidth			= 1;

			for (int y = 1; y < referenceLineMap.getHeight() - 1; ++y)
			{
				const bool	result	= compareColors(surface.getPixel(x, y), tcu::RGBA::white(), args.redBits, args.greenBits, args.blueBits);
				int			lineID	= 0;

				// Which line does this fragment belong to?

				if (result)
				{
					bool multipleNearbyLines = false;

					for (int dy = -1; dy < 2; ++dy)
					for (int dx = -1; dx < 2; ++dx)
					{
						const int nearbyID = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();
						if (nearbyID)
						{
							if (lineID && lineID != nearbyID)
								multipleNearbyLines = true;
							lineID = nearbyID;
						}
					}

					if (multipleNearbyLines)
					{
						// Another line is too close, don't try to calculate width here
						previousPixelUndefined = true;
						continue;
					}
				}

				// Only line with id of lineID is nearby

				if (previousPixelUndefined)
				{
					// The line might have been overdrawn or not
					currentLine = lineID;
					currentWidth = 1;
					fullyVisibleLine = false;
					previousPixelUndefined = false;
				}
				else if (lineID == currentLine)
				{
					// Current line continues
					++currentWidth;
				}
				else if (lineID > currentLine)
				{
					// Another line was drawn over or the line ends
					currentLine = lineID;
					currentWidth = 1;
					fullyVisibleLine = true;
				}
				else
				{
					// The line ends
					if (fullyVisibleLine && lineIsXMajor[currentLine-1])
					{
						// check width
						if (currentWidth != lineWidth)
						{
							log << tcu::TestLog::Message << "\tInvalid line width at (" << x << ", " << y - currentWidth << ") - (" << x  << ", " << y - 1 << "). Detected width of " << currentWidth << ", expected " << lineWidth << tcu::TestLog::EndMessage;
							invalidWidthFound = true;
						}
					}

					currentLine = lineID;
					currentWidth = 1;
					fullyVisibleLine = false;
				}
			}
		}

		if (invalidWidthFound)
		{
			log << tcu::TestLog::Message << "Invalid line width found, image is not valid." << tcu::TestLog::EndMessage;
			allOK = false;
		}
		else
		{
			log << tcu::TestLog::Message << "Line widths are valid." << tcu::TestLog::EndMessage;
		}
	}

	//\todo [2013-10-24 jarkko].
	//Requirement 4. If two line segments share a common endpoint, and both segments are either
	//x-major (both left-to-right or both right-to-left) or y-major (both bottom-totop
	//or both top-to-bottom), then rasterizing both segments may not produce
	//duplicate fragments, nor may any fragments be omitted so as to interrupt
	//continuity of the connected segments.

	if (!imageShown)
	{
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result", surface)
			<< tcu::TestLog::EndImageSet;
	}

	return allOK;
}

struct SingleSampleNarrowLineCandidate
{
	int			lineNdx;
	tcu::IVec3	colorMin;
	tcu::IVec3	colorMax;
	tcu::Vec3	colorMinF;
	tcu::Vec3	colorMaxF;
	tcu::Vec3	valueRangeMin;
	tcu::Vec3	valueRangeMax;
};

void setMaskMapCoverageBitForLine (int bitNdx, const tcu::Vec2& screenSpaceP0, const tcu::Vec2& screenSpaceP1, float lineWidth, tcu::PixelBufferAccess maskMap)
{
	enum
	{
		MAX_PACKETS = 32,
	};

	rr::SingleSampleLineRasterizer	rasterizer				(tcu::IVec4(0, 0, maskMap.getWidth(), maskMap.getHeight()));
	int								numRasterized			= MAX_PACKETS;
	rr::FragmentPacket				packets[MAX_PACKETS];

	rasterizer.init(tcu::Vec4(screenSpaceP0.x(), screenSpaceP0.y(), 0.0f, 1.0f),
					tcu::Vec4(screenSpaceP1.x(), screenSpaceP1.y(), 0.0f, 1.0f),
					lineWidth);

	while (numRasterized == MAX_PACKETS)
	{
		rasterizer.rasterize(packets, DE_NULL, MAX_PACKETS, numRasterized);

		for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
		{
			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			{
				if ((deUint32)packets[packetNdx].coverage & (1 << fragNdx))
				{
					const tcu::IVec2	fragPos			= packets[packetNdx].position + tcu::IVec2(fragNdx%2, fragNdx/2);

					DE_ASSERT(deInBounds32(fragPos.x(), 0, maskMap.getWidth()));
					DE_ASSERT(deInBounds32(fragPos.y(), 0, maskMap.getHeight()));

					const deUint32		previousMask	= maskMap.getPixelUint(fragPos.x(), fragPos.y()).x();
					const deUint32		newMask			= (previousMask) | ((deUint32)1u << bitNdx);

					maskMap.setPixel(tcu::UVec4(newMask, 0, 0, 0), fragPos.x(), fragPos.y());
				}
			}
		}
	}
}

void setMaskMapCoverageBitForLines (const std::vector<tcu::Vec4>& screenspaceLines, float lineWidth, tcu::PixelBufferAccess maskMap)
{
	for (int lineNdx = 0; lineNdx < (int)screenspaceLines.size(); ++lineNdx)
	{
		const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1);
		const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3);

		setMaskMapCoverageBitForLine(lineNdx, pa, pb, lineWidth, maskMap);
	}
}

// verify line interpolation assuming line pixels are interpolated independently depending only on screen space location
bool verifyLineGroupPixelIndependentInterpolation (const tcu::Surface&				surface,
												   const LineSceneSpec&				scene,
												   const RasterizationArguments&	args,
												   tcu::TestLog&					log,
												   LineInterpolationMethod			interpolationMethod)
{
	DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints
	DE_ASSERT(interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT || interpolationMethod == LINEINTERPOLATION_PROJECTED);

	const tcu::RGBA			invalidPixelColor	= tcu::RGBA(255, 0, 0, 255);
	const tcu::IVec2		viewportSize		= tcu::IVec2(surface.getWidth(), surface.getHeight());
	const int				errorFloodThreshold	= 4;
	int						errorCount			= 0;
	tcu::Surface			errorMask			(surface.getWidth(), surface.getHeight());
	int						invalidPixels		= 0;
	std::vector<tcu::Vec4>	screenspaceLines	(scene.lines.size()); //!< packed (x0, y0, x1, y1)

	// Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield
	// The map is used to find lines with potential coverage to a given pixel
	tcu::TextureLevel		referenceLineMap	(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());

	tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));

	// log format

	log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
	if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
		log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage;

	// prepare lookup map

	genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize);
	setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess());

	// Find all possible lines with coverage, check pixel color matches one of them

	for (int y = 1; y < surface.getHeight() - 1; ++y)
	for (int x = 1; x < surface.getWidth()  - 1; ++x)
	{
		const tcu::RGBA		color					= surface.getPixel(x, y);
		const tcu::IVec3	pixelNativeColor		= convertRGB8ToNativeFormat(color, args);	// Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
		int					lineCoverageSet			= 0;										// !< lines that may cover this fragment
		int					lineSurroundingCoverage = 0xFFFF;									// !< lines that will cover this fragment
		bool				matchFound				= false;
		const tcu::IVec3	formatLimit				((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);

		std::vector<SingleSampleNarrowLineCandidate> candidates;

		// Find lines with possible coverage

		for (int dy = -1; dy < 2; ++dy)
		for (int dx = -1; dx < 2; ++dx)
		{
			const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();

			lineCoverageSet			|= coverage;
			lineSurroundingCoverage	&= coverage;
		}

		// background color is possible?
		if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits))
			continue;

		// Check those lines

		for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
		{
			if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
			{
				const float						wa				= scene.lines[lineNdx].positions[0].w();
				const float						wb				= scene.lines[lineNdx].positions[1].w();
				const tcu::Vec2					pa				= screenspaceLines[lineNdx].swizzle(0, 1);
				const tcu::Vec2					pb				= screenspaceLines[lineNdx].swizzle(2, 3);

				const LineInterpolationRange	range			= (interpolationMethod == LINEINTERPOLATION_STRICTLY_CORRECT)
																	? (calcSingleSampleLineInterpolationRange(pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits))
																	: (calcSingleSampleLineInterpolationRangeAxisProjected(pa, wa, pb, wb, tcu::IVec2(x, y), args.subpixelBits));

				const tcu::Vec4					valueMin		= de::clamp(range.min.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.min.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
				const tcu::Vec4					valueMax		= de::clamp(range.max.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.max.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];

				const tcu::Vec3					colorMinF		(de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
																 de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
																 de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
				const tcu::Vec3					colorMaxF		(de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
																 de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
																 de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
				const tcu::IVec3				colorMin		((int)deFloatFloor(colorMinF.x()),
																 (int)deFloatFloor(colorMinF.y()),
																 (int)deFloatFloor(colorMinF.z()));
				const tcu::IVec3				colorMax		((int)deFloatCeil (colorMaxF.x()),
																 (int)deFloatCeil (colorMaxF.y()),
																 (int)deFloatCeil (colorMaxF.z()));

				// Verify validity
				if (pixelNativeColor.x() < colorMin.x() ||
					pixelNativeColor.y() < colorMin.y() ||
					pixelNativeColor.z() < colorMin.z() ||
					pixelNativeColor.x() > colorMax.x() ||
					pixelNativeColor.y() > colorMax.y() ||
					pixelNativeColor.z() > colorMax.z())
				{
					if (errorCount < errorFloodThreshold)
					{
						// Store candidate information for logging
						SingleSampleNarrowLineCandidate candidate;

						candidate.lineNdx		= lineNdx;
						candidate.colorMin		= colorMin;
						candidate.colorMax		= colorMax;
						candidate.colorMinF		= colorMinF;
						candidate.colorMaxF		= colorMaxF;
						candidate.valueRangeMin	= valueMin.swizzle(0, 1, 2);
						candidate.valueRangeMax	= valueMax.swizzle(0, 1, 2);

						candidates.push_back(candidate);
					}
				}
				else
				{
					matchFound = true;
					break;
				}
			}
		}

		if (matchFound)
			continue;

		// invalid fragment
		++invalidPixels;
		errorMask.setPixel(x, y, invalidPixelColor);

		++errorCount;

		// don't fill the logs with too much data
		if (errorCount < errorFloodThreshold)
		{
			log << tcu::TestLog::Message
				<< "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n"
				<< "\tPixel color:\t\t" << color << "\n"
				<< "\tNative color:\t\t" << pixelNativeColor << "\n"
				<< tcu::TestLog::EndMessage;

			for (int candidateNdx = 0; candidateNdx < (int)candidates.size(); ++candidateNdx)
			{
				const SingleSampleNarrowLineCandidate& candidate = candidates[candidateNdx];

				log << tcu::TestLog::Message << "\tCandidate (line " << candidate.lineNdx << "):\n"
					<< "\t\tReference native color min: " << tcu::clamp(candidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n"
					<< "\t\tReference native color max: " << tcu::clamp(candidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n"
					<< "\t\tReference native float min: " << tcu::clamp(candidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
					<< "\t\tReference native float max: " << tcu::clamp(candidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
					<< "\t\tFmin:\t" << tcu::clamp(candidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
					<< "\t\tFmax:\t" << tcu::clamp(candidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
					<< tcu::TestLog::EndMessage;
			}
		}
	}

	// don't just hide failures
	if (errorCount > errorFloodThreshold)
		log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage;

	// report result
	if (invalidPixels)
	{
		log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result",			surface)
			<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
			<< tcu::TestLog::EndImageSet;

		return false;
	}
	else
	{
		log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result", surface)
			<< tcu::TestLog::EndImageSet;

		return true;
	}
}

bool verifySinglesampleNarrowLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	DE_ASSERT(scene.lineWidth == 1.0f);
	return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_STRICTLY_CORRECT);
}

bool verifyLineGroupInterpolationWithProjectedWeights (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	return verifyLineGroupPixelIndependentInterpolation(surface, scene, args, log, LINEINTERPOLATION_PROJECTED);
}

struct SingleSampleWideLineCandidate
{
	struct InterpolationPointCandidate
	{
		tcu::IVec2	interpolationPoint;
		tcu::IVec3	colorMin;
		tcu::IVec3	colorMax;
		tcu::Vec3	colorMinF;
		tcu::Vec3	colorMaxF;
		tcu::Vec3	valueRangeMin;
		tcu::Vec3	valueRangeMax;
	};

	int							lineNdx;
	int							numCandidates;
	InterpolationPointCandidate	interpolationCandidates[3];
};

// return point on line at a given position on a given axis
tcu::Vec2 getLineCoordAtAxisCoord (const tcu::Vec2& pa, const tcu::Vec2& pb, bool isXAxis, float axisCoord)
{
	const int	fixedCoordNdx		= (isXAxis) ? (0) : (1);
	const int	varyingCoordNdx		= (isXAxis) ? (1) : (0);

	const float	fixedDifference		= pb[fixedCoordNdx] - pa[fixedCoordNdx];
	const float	varyingDifference	= pb[varyingCoordNdx] - pa[varyingCoordNdx];

	DE_ASSERT(fixedDifference != 0.0f);

	const float	resultFixedCoord	= axisCoord;
	const float	resultVaryingCoord	= pa[varyingCoordNdx] + (axisCoord - pa[fixedCoordNdx]) * (varyingDifference / fixedDifference);

	return (isXAxis) ? (tcu::Vec2(resultFixedCoord, resultVaryingCoord))
					 : (tcu::Vec2(resultVaryingCoord, resultFixedCoord));
}

bool isBlack (const tcu::RGBA& c)
{
	return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0;
}

bool verifySinglesampleWideLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	DE_ASSERT(deFloatFrac(scene.lineWidth) != 0.5f); // rounding direction is not defined, disallow undefined cases
	DE_ASSERT(scene.lines.size() < 8); // coverage indices are stored as bitmask in a unsigned 8-bit ints

	enum
	{
		FLAG_ROOT_NOT_SET = (1u << 16)
	};

	const tcu::RGBA						invalidPixelColor	= tcu::RGBA(255, 0, 0, 255);
	const tcu::IVec2					viewportSize		= tcu::IVec2(surface.getWidth(), surface.getHeight());
	const int							errorFloodThreshold	= 4;
	int									errorCount			= 0;
	tcu::Surface						errorMask			(surface.getWidth(), surface.getHeight());
	int									invalidPixels		= 0;
	std::vector<tcu::Vec4>				effectiveLines		(scene.lines.size()); //!< packed (x0, y0, x1, y1)
	std::vector<bool>					lineIsXMajor		(scene.lines.size());

	// for each line, for every distinct major direction fragment, store root pixel location (along
	// minor direction);
	std::vector<std::vector<deUint32> >	rootPixelLocation	(scene.lines.size()); //!< packed [16b - flags] [16b - coordinate]

	// log format

	log << tcu::TestLog::Message << "Verifying rasterization result. Native format is RGB" << args.redBits << args.greenBits << args.blueBits << tcu::TestLog::EndMessage;
	if (args.redBits > 8 || args.greenBits > 8 || args.blueBits > 8)
		log << tcu::TestLog::Message << "Warning! More than 8 bits in a color channel, this may produce false negatives." << tcu::TestLog::EndMessage;

	// Reference renderer produces correct fragments using the diamond-exit-rule. Make 2D int array, store line coverage as a 8-bit bitfield
	// The map is used to find lines with potential coverage to a given pixel
	tcu::TextureLevel referenceLineMap(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
	tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));

	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));

	// calculate mask and effective line coordinates
	{
		std::vector<tcu::Vec4> screenspaceLines(scene.lines.size());

		genScreenSpaceLines(screenspaceLines, scene.lines, viewportSize);
		setMaskMapCoverageBitForLines(screenspaceLines, scene.lineWidth, referenceLineMap.getAccess());

		for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
		{
			const tcu::Vec2	lineScreenSpaceP0	= screenspaceLines[lineNdx].swizzle(0, 1);
			const tcu::Vec2	lineScreenSpaceP1	= screenspaceLines[lineNdx].swizzle(2, 3);
			const bool		isXMajor			= isPackedSSLineXMajor(screenspaceLines[lineNdx]);

			lineIsXMajor[lineNdx] = isXMajor;

			// wide line interpolations are calculated for a line moved in minor direction
			{
				const float		offsetLength	= (scene.lineWidth - 1.0f) / 2.0f;
				const tcu::Vec2	offsetDirection	= (isXMajor) ? (tcu::Vec2(0.0f, -1.0f)) : (tcu::Vec2(-1.0f, 0.0f));
				const tcu::Vec2	offset			= offsetDirection * offsetLength;

				effectiveLines[lineNdx] = tcu::Vec4(lineScreenSpaceP0.x() + offset.x(),
													lineScreenSpaceP0.y() + offset.y(),
													lineScreenSpaceP1.x() + offset.x(),
													lineScreenSpaceP1.y() + offset.y());
			}
		}
	}

	for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
	{
		// Calculate root pixel lookup table for this line. Since the implementation's fragment
		// major coordinate range might not be a subset of the correct line range (they are allowed
		// to vary by one pixel), we must extend the domain to cover whole viewport along major
		// dimension.
		//
		// Expanding line strip to (effectively) infinite line might result in exit-diamnod set
		// that is not a superset of the exit-diamond set of the line strip. In practice, this
		// won't be an issue, since the allow-one-pixel-variation rule should tolerate this even
		// if the original and extended line would resolve differently a diamond the line just
		// touches (precision lost in expansion changes enter/exit status).

		{
			const bool						isXMajor			= lineIsXMajor[lineNdx];
			const int						majorSize			= (isXMajor) ? (surface.getWidth()) : (surface.getHeight());
			rr::LineExitDiamondGenerator	diamondGenerator;
			rr::LineExitDiamond				diamonds[32];
			int								numRasterized		= DE_LENGTH_OF_ARRAY(diamonds);

			// Expand to effectively infinite line (endpoints are just one pixel over viewport boundaries)
			const tcu::Vec2					expandedP0		= getLineCoordAtAxisCoord(effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3), isXMajor, -1.0f);
			const tcu::Vec2					expandedP1		= getLineCoordAtAxisCoord(effectiveLines[lineNdx].swizzle(0, 1), effectiveLines[lineNdx].swizzle(2, 3), isXMajor, (float)majorSize + 1.0f);

			diamondGenerator.init(tcu::Vec4(expandedP0.x(), expandedP0.y(), 0.0f, 1.0f),
								  tcu::Vec4(expandedP1.x(), expandedP1.y(), 0.0f, 1.0f));

			rootPixelLocation[lineNdx].resize(majorSize, FLAG_ROOT_NOT_SET);

			while (numRasterized == DE_LENGTH_OF_ARRAY(diamonds))
			{
				diamondGenerator.rasterize(diamonds, DE_LENGTH_OF_ARRAY(diamonds), numRasterized);

				for (int packetNdx = 0; packetNdx < numRasterized; ++packetNdx)
				{
					const tcu::IVec2	fragPos			= diamonds[packetNdx].position;
					const int			majorPos		= (isXMajor) ? (fragPos.x()) : (fragPos.y());
					const int			rootPos			= (isXMajor) ? (fragPos.y()) : (fragPos.x());
					const deUint32		packed			= (deUint32)((deUint16)((deInt16)rootPos));

					// infinite line will generate some diamonds outside the viewport
					if (deInBounds32(majorPos, 0, majorSize))
					{
						DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) != 0u);
						rootPixelLocation[lineNdx][majorPos] = packed;
					}
				}
			}

			// Filled whole lookup table
			for (int majorPos = 0; majorPos < majorSize; ++majorPos)
				DE_ASSERT((rootPixelLocation[lineNdx][majorPos] & FLAG_ROOT_NOT_SET) == 0u);
		}
	}

	// Find all possible lines with coverage, check pixel color matches one of them

	for (int y = 1; y < surface.getHeight() - 1; ++y)
	for (int x = 1; x < surface.getWidth()  - 1; ++x)
	{
		const tcu::RGBA		color					= surface.getPixel(x, y);
		const tcu::IVec3	pixelNativeColor		= convertRGB8ToNativeFormat(color, args);	// Convert pixel color from rgba8 to the real pixel format. Usually rgba8 or 565
		int					lineCoverageSet			= 0;										// !< lines that may cover this fragment
		int					lineSurroundingCoverage = 0xFFFF;									// !< lines that will cover this fragment
		bool				matchFound				= false;
		const tcu::IVec3	formatLimit				((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);

		std::vector<SingleSampleWideLineCandidate> candidates;

		// Find lines with possible coverage

		for (int dy = -1; dy < 2; ++dy)
		for (int dx = -1; dx < 2; ++dx)
		{
			const int coverage = referenceLineMap.getAccess().getPixelInt(x+dx, y+dy).x();

			lineCoverageSet			|= coverage;
			lineSurroundingCoverage	&= coverage;
		}

		// background color is possible?
		if (lineSurroundingCoverage == 0 && compareColors(color, tcu::RGBA::black(), args.redBits, args.greenBits, args.blueBits))
			continue;

		// Check those lines

		for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
		{
			if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
			{
				const float						wa				= scene.lines[lineNdx].positions[0].w();
				const float						wb				= scene.lines[lineNdx].positions[1].w();
				const tcu::Vec2					pa				= effectiveLines[lineNdx].swizzle(0, 1);
				const tcu::Vec2					pb				= effectiveLines[lineNdx].swizzle(2, 3);

				// \note Wide line fragments are generated by replicating the root fragment for each
				//       fragment column (row for y-major). Calculate interpolation at the root
				//       fragment.
				const bool						isXMajor		= lineIsXMajor[lineNdx];
				const int						majorPosition	= (isXMajor) ? (x) : (y);
				const deUint32					minorInfoPacked	= rootPixelLocation[lineNdx][majorPosition];
				const int						minorPosition	= (int)((deInt16)((deUint16)(minorInfoPacked & 0xFFFFu)));
				const tcu::IVec2				idealRootPos	= (isXMajor) ? (tcu::IVec2(majorPosition, minorPosition)) : (tcu::IVec2(minorPosition, majorPosition));
				const tcu::IVec2				minorDirection	= (isXMajor) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0));

				SingleSampleWideLineCandidate	candidate;

				candidate.lineNdx		= lineNdx;
				candidate.numCandidates	= 0;
				DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates) == 3);

				// Interpolation happens at the root fragment, which is then replicated in minor
				// direction. Search for implementation's root position near accurate root.
				for (int minorOffset = -1; minorOffset < 2; ++minorOffset)
				{
					const tcu::IVec2				rootPosition	= idealRootPos + minorOffset * minorDirection;

					// A fragment can be root fragment only if it exists
					// \note root fragment can "exist" outside viewport
					// \note no pixel format theshold since in this case allowing only black is more conservative
					if (deInBounds32(rootPosition.x(), 0, surface.getWidth()) &&
						deInBounds32(rootPosition.y(), 0, surface.getHeight()) &&
						isBlack(surface.getPixel(rootPosition.x(), rootPosition.y())))
					{
						continue;
					}

					const LineInterpolationRange	range			= calcSingleSampleLineInterpolationRange(pa, wa, pb, wb, rootPosition, args.subpixelBits);

					const tcu::Vec4					valueMin		= de::clamp(range.min.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.min.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];
					const tcu::Vec4					valueMax		= de::clamp(range.max.x(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[0] + de::clamp(range.max.y(), 0.0f, 1.0f) * scene.lines[lineNdx].colors[1];

					const tcu::Vec3					colorMinF		(de::clamp(valueMin.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
																	 de::clamp(valueMin.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
																	 de::clamp(valueMin.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
					const tcu::Vec3					colorMaxF		(de::clamp(valueMax.x() * (float)formatLimit.x(), 0.0f, (float)formatLimit.x()),
																	 de::clamp(valueMax.y() * (float)formatLimit.y(), 0.0f, (float)formatLimit.y()),
																	 de::clamp(valueMax.z() * (float)formatLimit.z(), 0.0f, (float)formatLimit.z()));
					const tcu::IVec3				colorMin		((int)deFloatFloor(colorMinF.x()),
																	 (int)deFloatFloor(colorMinF.y()),
																	 (int)deFloatFloor(colorMinF.z()));
					const tcu::IVec3				colorMax		((int)deFloatCeil (colorMaxF.x()),
																	 (int)deFloatCeil (colorMaxF.y()),
																	 (int)deFloatCeil (colorMaxF.z()));

					// Verify validity
					if (pixelNativeColor.x() < colorMin.x() ||
						pixelNativeColor.y() < colorMin.y() ||
						pixelNativeColor.z() < colorMin.z() ||
						pixelNativeColor.x() > colorMax.x() ||
						pixelNativeColor.y() > colorMax.y() ||
						pixelNativeColor.z() > colorMax.z())
					{
						if (errorCount < errorFloodThreshold)
						{
							// Store candidate information for logging
							SingleSampleWideLineCandidate::InterpolationPointCandidate& interpolationCandidate = candidate.interpolationCandidates[candidate.numCandidates++];
							DE_ASSERT(candidate.numCandidates <= DE_LENGTH_OF_ARRAY(candidate.interpolationCandidates));

							interpolationCandidate.interpolationPoint	= rootPosition;
							interpolationCandidate.colorMin				= colorMin;
							interpolationCandidate.colorMax				= colorMax;
							interpolationCandidate.colorMinF			= colorMinF;
							interpolationCandidate.colorMaxF			= colorMaxF;
							interpolationCandidate.valueRangeMin		= valueMin.swizzle(0, 1, 2);
							interpolationCandidate.valueRangeMax		= valueMax.swizzle(0, 1, 2);
						}
					}
					else
					{
						matchFound = true;
						break;
					}
				}

				if (!matchFound)
				{
					// store info for logging
					if (errorCount < errorFloodThreshold && candidate.numCandidates > 0)
						candidates.push_back(candidate);
				}
				else
				{
					// no need to check other lines
					break;
				}
			}
		}

		if (matchFound)
			continue;

		// invalid fragment
		++invalidPixels;
		errorMask.setPixel(x, y, invalidPixelColor);

		++errorCount;

		// don't fill the logs with too much data
		if (errorCount < errorFloodThreshold)
		{
			tcu::MessageBuilder msg(&log);

			msg	<< "Found an invalid pixel at (" << x << "," << y << "), " << (int)candidates.size() << " candidate reference value(s) found:\n"
				<< "\tPixel color:\t\t" << color << "\n"
				<< "\tNative color:\t\t" << pixelNativeColor << "\n";

			for (int lineCandidateNdx = 0; lineCandidateNdx < (int)candidates.size(); ++lineCandidateNdx)
			{
				const SingleSampleWideLineCandidate& candidate = candidates[lineCandidateNdx];

				msg << "\tCandidate line (line " << candidate.lineNdx << "):\n";

				for (int interpolationCandidateNdx = 0; interpolationCandidateNdx < candidate.numCandidates; ++interpolationCandidateNdx)
				{
					const SingleSampleWideLineCandidate::InterpolationPointCandidate& interpolationCandidate = candidate.interpolationCandidates[interpolationCandidateNdx];

					msg	<< "\t\tCandidate interpolation point (index " << interpolationCandidateNdx << "):\n"
						<< "\t\t\tRoot fragment position (non-replicated fragment): " << interpolationCandidate.interpolationPoint << ":\n"
						<< "\t\t\tReference native color min: " << tcu::clamp(interpolationCandidate.colorMin, tcu::IVec3(0,0,0), formatLimit) << "\n"
						<< "\t\t\tReference native color max: " << tcu::clamp(interpolationCandidate.colorMax, tcu::IVec3(0,0,0), formatLimit) << "\n"
						<< "\t\t\tReference native float min: " << tcu::clamp(interpolationCandidate.colorMinF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
						<< "\t\t\tReference native float max: " << tcu::clamp(interpolationCandidate.colorMaxF, tcu::Vec3(0.0f, 0.0f, 0.0f), formatLimit.cast<float>()) << "\n"
						<< "\t\t\tFmin:\t" << tcu::clamp(interpolationCandidate.valueRangeMin, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n"
						<< "\t\t\tFmax:\t" << tcu::clamp(interpolationCandidate.valueRangeMax, tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 1.0f)) << "\n";
				}
			}

			msg << tcu::TestLog::EndMessage;
		}
	}

	// don't just hide failures
	if (errorCount > errorFloodThreshold)
		log << tcu::TestLog::Message << "Omitted " << (errorCount-errorFloodThreshold) << " pixel error description(s)." << tcu::TestLog::EndMessage;

	// report result
	if (invalidPixels)
	{
		log << tcu::TestLog::Message << invalidPixels << " invalid pixel(s) found." << tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result",			surface)
			<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
			<< tcu::TestLog::EndImageSet;

		return false;
	}
	else
	{
		log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result", surface)
			<< tcu::TestLog::EndImageSet;

		return true;
	}
}

} // anonymous

CoverageType calculateTriangleCoverage (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2, const tcu::IVec2& pixel, const tcu::IVec2& viewportSize, int subpixelBits, bool multisample)
{
	typedef tcu::Vector<deInt64, 2> I64Vec2;

	const deUint64		numSubPixels						= ((deUint64)1) << subpixelBits;
	const deUint64		pixelHitBoxSize						= (multisample) ? (numSubPixels) : (2+2);	//!< allow 4 central (2x2) for non-multisample pixels. Rounding may move edges 1 subpixel to any direction.
	const bool			order								= isTriangleClockwise(p0, p1, p2);			//!< clockwise / counter-clockwise
	const tcu::Vec4&	orderedP0							= p0;										//!< vertices of a clockwise triangle
	const tcu::Vec4&	orderedP1							= (order) ? (p1) : (p2);
	const tcu::Vec4&	orderedP2							= (order) ? (p2) : (p1);
	const tcu::Vec2		triangleNormalizedDeviceSpace[3]	=
	{
		tcu::Vec2(orderedP0.x() / orderedP0.w(), orderedP0.y() / orderedP0.w()),
		tcu::Vec2(orderedP1.x() / orderedP1.w(), orderedP1.y() / orderedP1.w()),
		tcu::Vec2(orderedP2.x() / orderedP2.w(), orderedP2.y() / orderedP2.w()),
	};
	const tcu::Vec2		triangleScreenSpace[3]				=
	{
		(triangleNormalizedDeviceSpace[0] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
		(triangleNormalizedDeviceSpace[1] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
		(triangleNormalizedDeviceSpace[2] + tcu::Vec2(1.0f, 1.0f)) * 0.5f * tcu::Vec2((float)viewportSize.x(), (float)viewportSize.y()),
	};

	// Broad bounding box - pixel check
	{
		const float minX = de::min(de::min(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
		const float minY = de::min(de::min(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());
		const float maxX = de::max(de::max(triangleScreenSpace[0].x(), triangleScreenSpace[1].x()), triangleScreenSpace[2].x());
		const float maxY = de::max(de::max(triangleScreenSpace[0].y(), triangleScreenSpace[1].y()), triangleScreenSpace[2].y());

		if ((float)pixel.x() > maxX + 1 ||
			(float)pixel.y() > maxY + 1 ||
			(float)pixel.x() < minX - 1 ||
			(float)pixel.y() < minY - 1)
			return COVERAGE_NONE;
	}

	// Broad triangle - pixel area intersection
	{
		const I64Vec2 pixelCenterPosition = I64Vec2(pixel.x(), pixel.y()) * I64Vec2(numSubPixels, numSubPixels) + I64Vec2(numSubPixels / 2, numSubPixels / 2);
		const I64Vec2 triangleSubPixelSpaceRound[3] =
		{
			I64Vec2(deRoundFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deRoundFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
			I64Vec2(deRoundFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deRoundFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
			I64Vec2(deRoundFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deRoundFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
		};

		// Check (using cross product) if pixel center is
		// a) too far from any edge
		// b) fully inside all edges
		bool insideAllEdges = true;
		for (int vtxNdx = 0; vtxNdx < 3; ++vtxNdx)
		{
			const int		otherVtxNdx				= (vtxNdx + 1) % 3;
			const deInt64	maxPixelDistanceSquared	= pixelHitBoxSize*pixelHitBoxSize; // Max distance from the pixel center from within the pixel is (sqrt(2) * boxWidth/2). Use 2x value for rounding tolerance
			const I64Vec2	edge					= triangleSubPixelSpaceRound[otherVtxNdx]	- triangleSubPixelSpaceRound[vtxNdx];
			const I64Vec2	v						= pixelCenterPosition						- triangleSubPixelSpaceRound[vtxNdx];
			const deInt64	crossProduct			= (edge.x() * v.y() - edge.y() * v.x());

			// distance from edge: (edge x v) / |edge|
			//     (edge x v) / |edge| > maxPixelDistance
			// ==> (edge x v)^2 / edge^2 > maxPixelDistance^2    | edge x v > 0
			// ==> (edge x v)^2 > maxPixelDistance^2 * edge^2
			if (crossProduct < 0 && crossProduct*crossProduct > maxPixelDistanceSquared * tcu::lengthSquared(edge))
				return COVERAGE_NONE;
			if (crossProduct < 0 || crossProduct*crossProduct < maxPixelDistanceSquared * tcu::lengthSquared(edge))
				insideAllEdges = false;
		}

		if (insideAllEdges)
			return COVERAGE_FULL;
	}

	// Accurate intersection for edge pixels
	{
		//  In multisampling, the sample points can be anywhere in the pixel, and in single sampling only in the center.
		const I64Vec2 pixelCorners[4] =
		{
			I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+0) * numSubPixels),
			I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+0) * numSubPixels),
			I64Vec2((pixel.x()+1) * numSubPixels, (pixel.y()+1) * numSubPixels),
			I64Vec2((pixel.x()+0) * numSubPixels, (pixel.y()+1) * numSubPixels),
		};
		const I64Vec2 pixelCenterCorners[4] =
		{
			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 0),
			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 0),
			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 1, pixel.y() * numSubPixels + numSubPixels/2 + 1),
			I64Vec2(pixel.x() * numSubPixels + numSubPixels/2 + 0, pixel.y() * numSubPixels + numSubPixels/2 + 1),
		};

		// both rounding directions
		const I64Vec2 triangleSubPixelSpaceFloor[3] =
		{
			I64Vec2(deFloorFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
			I64Vec2(deFloorFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
			I64Vec2(deFloorFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deFloorFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
		};
		const I64Vec2 triangleSubPixelSpaceCeil[3] =
		{
			I64Vec2(deCeilFloatToInt32(triangleScreenSpace[0].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[0].y() * (float)numSubPixels)),
			I64Vec2(deCeilFloatToInt32(triangleScreenSpace[1].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[1].y() * (float)numSubPixels)),
			I64Vec2(deCeilFloatToInt32(triangleScreenSpace[2].x() * (float)numSubPixels), deCeilFloatToInt32(triangleScreenSpace[2].y() * (float)numSubPixels)),
		};
		const I64Vec2* const corners = (multisample) ? (pixelCorners) : (pixelCenterCorners);

		// Test if any edge (with any rounding) intersects the pixel (boundary). If it does => Partial. If not => fully inside or outside

		for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
		for (int startRounding = 0; startRounding < 4; ++startRounding)
		for (int endRounding = 0; endRounding < 4; ++endRounding)
		{
			const int		nextEdgeNdx	= (edgeNdx+1) % 3;
			const I64Vec2	startPos	((startRounding&0x01)	? (triangleSubPixelSpaceFloor[edgeNdx].x())		: (triangleSubPixelSpaceCeil[edgeNdx].x()),		(startRounding&0x02)	? (triangleSubPixelSpaceFloor[edgeNdx].y())		: (triangleSubPixelSpaceCeil[edgeNdx].y()));
			const I64Vec2	endPos		((endRounding&0x01)		? (triangleSubPixelSpaceFloor[nextEdgeNdx].x())	: (triangleSubPixelSpaceCeil[nextEdgeNdx].x()),	(endRounding&0x02)		? (triangleSubPixelSpaceFloor[nextEdgeNdx].y())	: (triangleSubPixelSpaceCeil[nextEdgeNdx].y()));

			for (int pixelEdgeNdx = 0; pixelEdgeNdx < 4; ++pixelEdgeNdx)
			{
				const int pixelEdgeEnd = (pixelEdgeNdx + 1) % 4;

				if (lineLineIntersect(startPos, endPos, corners[pixelEdgeNdx], corners[pixelEdgeEnd]))
					return COVERAGE_PARTIAL;
			}
		}

		// fully inside or outside
		for (int edgeNdx = 0; edgeNdx < 3; ++edgeNdx)
		{
			const int		nextEdgeNdx		= (edgeNdx+1) % 3;
			const I64Vec2&	startPos		= triangleSubPixelSpaceFloor[edgeNdx];
			const I64Vec2&	endPos			= triangleSubPixelSpaceFloor[nextEdgeNdx];
			const I64Vec2	edge			= endPos - startPos;
			const I64Vec2	v				= corners[0] - endPos;
			const deInt64	crossProduct	= (edge.x() * v.y() - edge.y() * v.x());

			// a corner of the pixel is outside => "fully inside" option is impossible
			if (crossProduct < 0)
				return COVERAGE_NONE;
		}

		return COVERAGE_FULL;
	}
}

bool verifyTriangleGroupRasterization (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log, VerificationMode mode)
{
	DE_ASSERT(mode < VERIFICATIONMODE_LAST);

	const tcu::RGBA		backGroundColor				= tcu::RGBA(0, 0, 0, 255);
	const tcu::RGBA		triangleColor				= tcu::RGBA(255, 255, 255, 255);
	const tcu::RGBA		missingPixelColor			= tcu::RGBA(255, 0, 255, 255);
	const tcu::RGBA		unexpectedPixelColor		= tcu::RGBA(255, 0, 0, 255);
	const tcu::RGBA		partialPixelColor			= tcu::RGBA(255, 255, 0, 255);
	const tcu::RGBA		primitivePixelColor			= tcu::RGBA(30, 30, 30, 255);
	const int			weakVerificationThreshold	= 10;
	const bool			multisampled				= (args.numSamples != 0);
	const tcu::IVec2	viewportSize				= tcu::IVec2(surface.getWidth(), surface.getHeight());
	int					missingPixels				= 0;
	int					unexpectedPixels			= 0;
	int					subPixelBits				= args.subpixelBits;
	tcu::TextureLevel	coverageMap					(tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT8), surface.getWidth(), surface.getHeight());
	tcu::Surface		errorMask					(surface.getWidth(), surface.getHeight());

	// subpixel bits in in a valid range?

	if (subPixelBits < 0)
	{
		log << tcu::TestLog::Message << "Invalid subpixel count (" << subPixelBits << "), assuming 0" << tcu::TestLog::EndMessage;
		subPixelBits = 0;
	}
	else if (subPixelBits > 16)
	{
		// At high subpixel bit counts we might overflow. Checking at lower bit count is ok, but is less strict
		log << tcu::TestLog::Message << "Subpixel count is greater than 16 (" << subPixelBits << "). Checking results using less strict 16 bit requirements. This may produce false positives." << tcu::TestLog::EndMessage;
		subPixelBits = 16;
	}

	// generate coverage map

	tcu::clear(coverageMap.getAccess(), tcu::IVec4(COVERAGE_NONE, 0, 0, 0));

	for (int triNdx = 0; triNdx < (int)scene.triangles.size(); ++triNdx)
	{
		const tcu::IVec4 aabb = getTriangleAABB(scene.triangles[triNdx], viewportSize);

		for (int y = de::max(0, aabb.y()); y <= de::min(aabb.w(), coverageMap.getHeight() - 1); ++y)
		for (int x = de::max(0, aabb.x()); x <= de::min(aabb.z(), coverageMap.getWidth() - 1); ++x)
		{
			if (coverageMap.getAccess().getPixelUint(x, y).x() == COVERAGE_FULL)
				continue;

			const CoverageType coverage = calculateTriangleCoverage(scene.triangles[triNdx].positions[0],
																	scene.triangles[triNdx].positions[1],
																	scene.triangles[triNdx].positions[2],
																	tcu::IVec2(x, y),
																	viewportSize,
																	subPixelBits,
																	multisampled);

			if (coverage == COVERAGE_FULL)
			{
				coverageMap.getAccess().setPixel(tcu::IVec4(COVERAGE_FULL, 0, 0, 0), x, y);
			}
			else if (coverage == COVERAGE_PARTIAL)
			{
				CoverageType resultCoverage = COVERAGE_PARTIAL;

				// Sharing an edge with another triangle?
				// There should always be such a triangle, but the pixel in the other triangle might be
				// on multiple edges, some of which are not shared. In these cases the coverage cannot be determined.
				// Assume full coverage if the pixel is only on a shared edge in shared triangle too.
				if (pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[triNdx], viewportSize))
				{
					bool friendFound = false;
					for (int friendTriNdx = 0; friendTriNdx < (int)scene.triangles.size(); ++friendTriNdx)
					{
						if (friendTriNdx != triNdx && pixelOnlyOnASharedEdge(tcu::IVec2(x, y), scene.triangles[friendTriNdx], viewportSize))
						{
							friendFound = true;
							break;
						}
					}

					if (friendFound)
						resultCoverage = COVERAGE_FULL;
				}

				coverageMap.getAccess().setPixel(tcu::IVec4(resultCoverage, 0, 0, 0), x, y);
			}
		}
	}

	// check pixels

	tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));

	for (int y = 0; y < surface.getHeight(); ++y)
	for (int x = 0; x < surface.getWidth(); ++x)
	{
		const tcu::RGBA		color				= surface.getPixel(x, y);
		const bool			imageNoCoverage		= compareColors(color, backGroundColor, args.redBits, args.greenBits, args.blueBits);
		const bool			imageFullCoverage	= compareColors(color, triangleColor, args.redBits, args.greenBits, args.blueBits);
		CoverageType		referenceCoverage	= (CoverageType)coverageMap.getAccess().getPixelUint(x, y).x();

		switch (referenceCoverage)
		{
			case COVERAGE_NONE:
				if (!imageNoCoverage)
				{
					// coverage where there should not be
					++unexpectedPixels;
					errorMask.setPixel(x, y, unexpectedPixelColor);
				}
				break;

			case COVERAGE_PARTIAL:
				// anything goes
				errorMask.setPixel(x, y, partialPixelColor);
				break;

			case COVERAGE_FULL:
				if (!imageFullCoverage)
				{
					// no coverage where there should be
					++missingPixels;
					errorMask.setPixel(x, y, missingPixelColor);
				}
				else
				{
					errorMask.setPixel(x, y, primitivePixelColor);
				}
				break;

			default:
				DE_ASSERT(false);
		};
	}

	// Output results
	log << tcu::TestLog::Message << "Verifying rasterization result." << tcu::TestLog::EndMessage;

	if (((mode == VERIFICATIONMODE_STRICT) && (missingPixels + unexpectedPixels > 0)) ||
		((mode == VERIFICATIONMODE_WEAK)   && (missingPixels + unexpectedPixels > weakVerificationThreshold)))
	{
		log << tcu::TestLog::Message << "Invalid pixels found:\n\t"
			<< missingPixels << " missing pixels. (Marked with purple)\n\t"
			<< unexpectedPixels << " incorrectly filled pixels. (Marked with red)\n\t"
			<< "Unknown (subpixel on edge) pixels are marked with yellow."
			<< tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result",	"Result",		surface)
			<< tcu::TestLog::Image("ErrorMask", "ErrorMask",	errorMask)
			<< tcu::TestLog::EndImageSet;

		return false;
	}
	else
	{
		log << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
		log << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
			<< tcu::TestLog::Image("Result", "Result", surface)
			<< tcu::TestLog::EndImageSet;

		return true;
	}
}

bool verifyLineGroupRasterization (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	const bool multisampled = args.numSamples != 0;

	if (multisampled)
		return verifyMultisampleLineGroupRasterization(surface, scene, args, log);
	else
		return verifySinglesampleLineGroupRasterization(surface, scene, args, log);
}

bool verifyPointGroupRasterization (const tcu::Surface& surface, const PointSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	// Splitting to triangles is a valid solution in multisampled cases and even in non-multisample cases too.
	return verifyMultisamplePointGroupRasterization(surface, scene, args, log);
}

bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const TriangleSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	return verifyTriangleGroupInterpolationWithInterpolator(surface, scene, args, log, TriangleInterpolator(scene));
}

LineInterpolationMethod verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
{
	const bool multisampled = args.numSamples != 0;

	if (multisampled)
	{
		if (verifyMultisampleLineGroupInterpolation(surface, scene, args, log))
			return LINEINTERPOLATION_STRICTLY_CORRECT;
		return LINEINTERPOLATION_INCORRECT;
	}
	else
	{
		const bool isNarrow = (scene.lineWidth == 1.0f);

		// accurate interpolation
		if (isNarrow)
		{
			if (verifySinglesampleNarrowLineGroupInterpolation(surface, scene, args, log))
				return LINEINTERPOLATION_STRICTLY_CORRECT;
		}
		else
		{
			if (verifySinglesampleWideLineGroupInterpolation(surface, scene, args, log))
				return LINEINTERPOLATION_STRICTLY_CORRECT;
		}

		// check with projected (inaccurate) interpolation
		log << tcu::TestLog::Message << "Accurate verification failed, checking with projected weights (inaccurate equation)." << tcu::TestLog::EndMessage;
		if (verifyLineGroupInterpolationWithProjectedWeights(surface, scene, args, log))
			return LINEINTERPOLATION_PROJECTED;

		return LINEINTERPOLATION_INCORRECT;
	}
}

} // StateQueryUtil
} // gls
} // deqp