C++程序  |  1047行  |  39.26 KB

/*-------------------------------------------------------------------------
 * drawElements Quality Program Reference Renderer
 * -----------------------------------------------
 *
 * 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 Reference rasterizer
 *//*--------------------------------------------------------------------*/

#include "rrRasterizer.hpp"
#include "deMath.h"
#include "tcuVectorUtil.hpp"

namespace rr
{

inline deInt64 toSubpixelCoord (float v)
{
	return (deInt64)(v * (1<<RASTERIZER_SUBPIXEL_BITS) + (v < 0.f ? -0.5f : 0.5f));
}

inline deInt64 toSubpixelCoord (deInt32 v)
{
	return v << RASTERIZER_SUBPIXEL_BITS;
}

inline deInt32 ceilSubpixelToPixelCoord (deInt64 coord, bool fillEdge)
{
	if (coord >= 0)
		return (deInt32)((coord + ((1ll<<RASTERIZER_SUBPIXEL_BITS) - (fillEdge ? 0 : 1))) >> RASTERIZER_SUBPIXEL_BITS);
	else
		return (deInt32)((coord + (fillEdge ? 1 : 0)) >> RASTERIZER_SUBPIXEL_BITS);
}

inline deInt32 floorSubpixelToPixelCoord (deInt64 coord, bool fillEdge)
{
	if (coord >= 0)
		return (deInt32)((coord - (fillEdge ? 1 : 0)) >> RASTERIZER_SUBPIXEL_BITS);
	else
		return (deInt32)((coord - ((1ll<<RASTERIZER_SUBPIXEL_BITS) - (fillEdge ? 0 : 1))) >> RASTERIZER_SUBPIXEL_BITS);
}

static inline void initEdgeCCW (EdgeFunction& edge, const HorizontalFill horizontalFill, const VerticalFill verticalFill, const deInt64 x0, const deInt64 y0, const deInt64 x1, const deInt64 y1)
{
	// \note See EdgeFunction documentation for details.

	const deInt64	xd			= x1-x0;
	const deInt64	yd			= y1-y0;
	bool			inclusive	= false;	//!< Inclusive in CCW orientation.

	if (yd == 0)
		inclusive = verticalFill == FILL_BOTTOM ? xd >= 0 : xd <= 0;
	else
		inclusive = horizontalFill == FILL_LEFT ? yd <= 0 : yd >= 0;

	edge.a			= (y0 - y1);
	edge.b			= (x1 - x0);
	edge.c			= x0*y1 - y0*x1;
	edge.inclusive	= inclusive; //!< \todo [pyry] Swap for CW triangles
}

static inline void reverseEdge (EdgeFunction& edge)
{
	edge.a			= -edge.a;
	edge.b			= -edge.b;
	edge.c			= -edge.c;
	edge.inclusive	= !edge.inclusive;
}

static inline deInt64 evaluateEdge (const EdgeFunction& edge, const deInt64 x, const deInt64 y)
{
	return edge.a*x + edge.b*y + edge.c;
}

static inline bool isInsideCCW (const EdgeFunction& edge, const deInt64 edgeVal)
{
	return edge.inclusive ? (edgeVal >= 0) : (edgeVal > 0);
}

namespace LineRasterUtil
{

struct SubpixelLineSegment
{
	const tcu::Vector<deInt64,2>	m_v0;
	const tcu::Vector<deInt64,2>	m_v1;

	SubpixelLineSegment (const tcu::Vector<deInt64,2>& v0, const tcu::Vector<deInt64,2>& v1)
		: m_v0(v0)
		, m_v1(v1)
	{
	}

	tcu::Vector<deInt64,2> direction (void) const
	{
		return m_v1 - m_v0;
	}
};

enum LINE_SIDE
{
	LINE_SIDE_INTERSECT = 0,
	LINE_SIDE_LEFT,
	LINE_SIDE_RIGHT
};

static tcu::Vector<deInt64,2> toSubpixelVector (const tcu::Vec2& v)
{
	return tcu::Vector<deInt64,2>(toSubpixelCoord(v.x()), toSubpixelCoord(v.y()));
}

static tcu::Vector<deInt64,2> toSubpixelVector (const tcu::IVec2& v)
{
	return tcu::Vector<deInt64,2>(toSubpixelCoord(v.x()), toSubpixelCoord(v.y()));
}

#if defined(DE_DEBUG)
static bool isTheCenterOfTheFragment (const tcu::Vector<deInt64,2>& a)
{
	const deUint64 pixelSize = 1ll << (RASTERIZER_SUBPIXEL_BITS);
	const deUint64 halfPixel = 1ll << (RASTERIZER_SUBPIXEL_BITS-1);
	return	((a.x() & (pixelSize-1)) == halfPixel &&
				(a.y() & (pixelSize-1)) == halfPixel);
}

static bool inViewport (const tcu::IVec2& p, const tcu::IVec4& viewport)
{
	return	p.x() >= viewport.x() &&
			p.y() >= viewport.y() &&
			p.x() <  viewport.x() + viewport.z() &&
			p.y() <  viewport.y() + viewport.w();
}
#endif // DE_DEBUG

// returns true if vertex is on the left side of the line
static bool vertexOnLeftSideOfLine (const tcu::Vector<deInt64,2>& p, const SubpixelLineSegment& l)
{
	const tcu::Vector<deInt64,2> u = l.direction();
	const tcu::Vector<deInt64,2> v = ( p - l.m_v0);
	const deInt64 crossProduct = (u.x() * v.y() - u.y() * v.x());
	return crossProduct < 0;
}

// returns true if vertex is on the right side of the line
static bool vertexOnRightSideOfLine (const tcu::Vector<deInt64,2>& p, const SubpixelLineSegment& l)
{
	const tcu::Vector<deInt64,2> u = l.direction();
	const tcu::Vector<deInt64,2> v = ( p - l.m_v0);
	const deInt64 crossProduct = (u.x() * v.y() - u.y() * v.x());
	return crossProduct > 0;
}

// returns true if vertex is on the line
static bool vertexOnLine (const tcu::Vector<deInt64,2>& p, const SubpixelLineSegment& l)
{
	const tcu::Vector<deInt64,2> u = l.direction();
	const tcu::Vector<deInt64,2> v = ( p - l.m_v0);
	const deInt64 crossProduct = (u.x() * v.y() - u.y() * v.x());
	return crossProduct == 0; // cross product == 0
}

// returns true if vertex is on the line segment
static bool vertexOnLineSegment (const tcu::Vector<deInt64,2>& p, const SubpixelLineSegment& l)
{
	if (!vertexOnLine(p, l))
		return false;

	const tcu::Vector<deInt64,2> v	= l.direction();
	const tcu::Vector<deInt64,2> u1	= ( p - l.m_v0);
	const tcu::Vector<deInt64,2> u2	= ( p - l.m_v1);

	if (v.x() == 0 && v.y() == 0)
		return false;

	return	tcu::dot( v, u1) >= 0 &&
			tcu::dot(-v, u2) >= 0; // dot (A->B, A->V) >= 0 and dot (B->A, B->V) >= 0
}

static LINE_SIDE getVertexSide (const tcu::Vector<deInt64,2>& v, const SubpixelLineSegment& l)
{
	if (vertexOnLeftSideOfLine(v, l))
		return LINE_SIDE_LEFT;
	else if (vertexOnRightSideOfLine(v, l))
		return LINE_SIDE_RIGHT;
	else if (vertexOnLine(v, l))
		return LINE_SIDE_INTERSECT;
	else
	{
		DE_ASSERT(false);
		return LINE_SIDE_INTERSECT;
	}
}

// returns true if angle between line and given cornerExitNormal is in range (-45, 45)
bool lineInCornerAngleRange (const SubpixelLineSegment& line, const tcu::Vector<deInt64,2>& cornerExitNormal)
{
	// v0 -> v1 has angle difference to cornerExitNormal in range (-45, 45)
	const tcu::Vector<deInt64,2> v = line.direction();
	const deInt64 dotProduct = dot(v, cornerExitNormal);

	// dotProduct > |v1-v0|*|cornerExitNormal|/sqrt(2)
	if (dotProduct < 0)
		return false;
	return 2 * dotProduct * dotProduct > tcu::lengthSquared(v)*tcu::lengthSquared(cornerExitNormal);
}

// returns true if angle between line and given cornerExitNormal is in range (-135, 135)
bool lineInCornerOutsideAngleRange (const SubpixelLineSegment& line, const tcu::Vector<deInt64,2>& cornerExitNormal)
{
	// v0 -> v1 has angle difference to cornerExitNormal in range (-135, 135)
	const tcu::Vector<deInt64,2> v = line.direction();
	const deInt64 dotProduct = dot(v, cornerExitNormal);

	// dotProduct > -|v1-v0|*|cornerExitNormal|/sqrt(2)
	if (dotProduct >= 0)
		return true;
	return 2 * (-dotProduct) * (-dotProduct) < tcu::lengthSquared(v)*tcu::lengthSquared(cornerExitNormal);
}

bool doesLineSegmentExitDiamond (const SubpixelLineSegment& line, const tcu::Vector<deInt64,2>& diamondCenter)
{
	DE_ASSERT(isTheCenterOfTheFragment(diamondCenter));

	// Diamond Center is at diamondCenter in subpixel coords

	const deInt64 halfPixel = 1ll << (RASTERIZER_SUBPIXEL_BITS-1);

	const struct DiamondBound
	{
		tcu::Vector<deInt64,2>	p0;
		tcu::Vector<deInt64,2>	p1;
		bool					edgeInclusive; // would a point on the bound be inside of the region
	} bounds[] =
	{
		{ diamondCenter + tcu::Vector<deInt64,2>(0,				-halfPixel),	diamondCenter + tcu::Vector<deInt64,2>(-halfPixel,	0),				 false	},
		{ diamondCenter + tcu::Vector<deInt64,2>(-halfPixel,	0),				diamondCenter + tcu::Vector<deInt64,2>(0,			halfPixel),		 false	},
		{ diamondCenter + tcu::Vector<deInt64,2>(0,				halfPixel),		diamondCenter + tcu::Vector<deInt64,2>(halfPixel,	0),				 true	},
		{ diamondCenter + tcu::Vector<deInt64,2>(halfPixel,		0),				diamondCenter + tcu::Vector<deInt64,2>(0,			-halfPixel),	 true	},
	};

	const struct DiamondCorners
	{
		enum CORNER_EDGE_CASE_BEHAVIOR
		{
			CORNER_EDGE_CASE_NONE,							// if the line intersects just a corner, no entering or exiting
			CORNER_EDGE_CASE_HIT,							// if the line intersects just a corner, entering and exit
			CORNER_EDGE_CASE_HIT_FIRST_QUARTER,				// if the line intersects just a corner and the line has either endpoint in (+X,-Y) direction (preturbing moves the line inside)
			CORNER_EDGE_CASE_HIT_SECOND_QUARTER				// if the line intersects just a corner and the line has either endpoint in (+X,+Y) direction (preturbing moves the line inside)
		};
		enum CORNER_START_CASE_BEHAVIOR
		{
			CORNER_START_CASE_NONE,							// the line starting point is outside, no exiting
			CORNER_START_CASE_OUTSIDE,						// exit, if line does not intersect the region (preturbing moves the start point inside)
			CORNER_START_CASE_POSITIVE_Y_45,				// exit, if line the angle of line vector and X-axis is in range (0, 45] in positive Y side.
			CORNER_START_CASE_NEGATIVE_Y_45					// exit, if line the angle of line vector and X-axis is in range [0, 45] in negative Y side.
		};
		enum CORNER_END_CASE_BEHAVIOR
		{
			CORNER_END_CASE_NONE,							// end is inside, no exiting (preturbing moves the line end inside)
			CORNER_END_CASE_DIRECTION,						// exit, if line intersected the region (preturbing moves the line end outside)
			CORNER_END_CASE_DIRECTION_AND_FIRST_QUARTER,	// exit, if line intersected the region, or line originates from (+X,-Y) direction (preturbing moves the line end outside)
			CORNER_END_CASE_DIRECTION_AND_SECOND_QUARTER	// exit, if line intersected the region, or line originates from (+X,+Y) direction (preturbing moves the line end outside)
		};

		tcu::Vector<deInt64,2>		dp;
		bool						pointInclusive;			// would a point in this corner intersect with the region
		CORNER_EDGE_CASE_BEHAVIOR	lineBehavior;			// would a line segment going through this corner intersect with the region
		CORNER_START_CASE_BEHAVIOR	startBehavior;			// how the corner behaves if the start point at the corner
		CORNER_END_CASE_BEHAVIOR	endBehavior;			// how the corner behaves if the end point at the corner

	} corners[] =
	{
		{ tcu::Vector<deInt64,2>(0,				-halfPixel),	false,	DiamondCorners::CORNER_EDGE_CASE_HIT_SECOND_QUARTER,	DiamondCorners::CORNER_START_CASE_POSITIVE_Y_45,	DiamondCorners::CORNER_END_CASE_DIRECTION_AND_SECOND_QUARTER},
		{ tcu::Vector<deInt64,2>(-halfPixel,	0),				false,	DiamondCorners::CORNER_EDGE_CASE_NONE,					DiamondCorners::CORNER_START_CASE_NONE,				DiamondCorners::CORNER_END_CASE_DIRECTION					},
		{ tcu::Vector<deInt64,2>(0,				halfPixel),		false,	DiamondCorners::CORNER_EDGE_CASE_HIT_FIRST_QUARTER,		DiamondCorners::CORNER_START_CASE_NEGATIVE_Y_45,	DiamondCorners::CORNER_END_CASE_DIRECTION_AND_FIRST_QUARTER	},
		{ tcu::Vector<deInt64,2>(halfPixel,		0),				true,	DiamondCorners::CORNER_EDGE_CASE_HIT,					DiamondCorners::CORNER_START_CASE_OUTSIDE,			DiamondCorners::CORNER_END_CASE_NONE						},
	};

	// Corner cases at the corners
	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(corners); ++ndx)
	{
		const tcu::Vector<deInt64,2> p	= diamondCenter + corners[ndx].dp;
		const bool intersectsAtCorner	= LineRasterUtil::vertexOnLineSegment(p, line);

		if (!intersectsAtCorner)
			continue;

		// line segment body intersects with the corner
		if (p != line.m_v0 && p != line.m_v1)
		{
			if (corners[ndx].lineBehavior == DiamondCorners::CORNER_EDGE_CASE_HIT)
				return true;

			// endpoint in (+X, -Y) (X or Y may be 0) direction <==> x*y <= 0
			if (corners[ndx].lineBehavior == DiamondCorners::CORNER_EDGE_CASE_HIT_FIRST_QUARTER &&
				(line.direction().x() * line.direction().y()) <= 0)
				return true;

			// endpoint in (+X, +Y) (Y > 0) direction <==> x*y > 0
			if (corners[ndx].lineBehavior == DiamondCorners::CORNER_EDGE_CASE_HIT_SECOND_QUARTER &&
				(line.direction().x() * line.direction().y()) > 0)
				return true;
		}

		// line exits the area at the corner
		if (lineInCornerAngleRange(line, corners[ndx].dp))
		{
			const bool startIsInside = corners[ndx].pointInclusive || p != line.m_v0;
			const bool endIsOutside = !corners[ndx].pointInclusive || p != line.m_v1;

			// starting point is inside the region and end endpoint is outside
			if (startIsInside && endIsOutside)
				return true;
		}

		// line end is at the corner
		if (p == line.m_v1)
		{
			if (corners[ndx].endBehavior == DiamondCorners::CORNER_END_CASE_DIRECTION ||
				corners[ndx].endBehavior == DiamondCorners::CORNER_END_CASE_DIRECTION_AND_FIRST_QUARTER ||
				corners[ndx].endBehavior == DiamondCorners::CORNER_END_CASE_DIRECTION_AND_SECOND_QUARTER)
			{
				// did the line intersect the region
				if (lineInCornerAngleRange(line, corners[ndx].dp))
					return true;
			}

			// due to the perturbed endpoint, lines at this the angle will cause and enter-exit pair
			if (corners[ndx].endBehavior == DiamondCorners::CORNER_END_CASE_DIRECTION_AND_FIRST_QUARTER &&
				line.direction().x() < 0 &&
				line.direction().y() > 0)
				return true;
			if (corners[ndx].endBehavior == DiamondCorners::CORNER_END_CASE_DIRECTION_AND_SECOND_QUARTER &&
				line.direction().x() > 0 &&
				line.direction().y() > 0)
				return true;
		}

		// line start is at the corner
		if (p == line.m_v0)
		{
			if (corners[ndx].startBehavior == DiamondCorners::CORNER_START_CASE_OUTSIDE)
			{
				// if the line is not going inside, it will exit
				if (lineInCornerOutsideAngleRange(line, corners[ndx].dp))
					return true;
			}

			// exit, if line the angle between line vector and X-axis is in range (0, 45] in positive Y side.
			if (corners[ndx].startBehavior == DiamondCorners::CORNER_START_CASE_POSITIVE_Y_45 &&
				line.direction().x() > 0 &&
				line.direction().y() > 0 &&
				line.direction().y() <= line.direction().x())
				return true;

			// exit, if line the angle between line vector and X-axis is in range [0, 45] in negative Y side.
			if (corners[ndx].startBehavior == DiamondCorners::CORNER_START_CASE_NEGATIVE_Y_45 &&
				 line.direction().x() > 0 &&
				 line.direction().y() <= 0 &&
				-line.direction().y() <= line.direction().x())
				return true;
		}
	}

	// Does the line intersect boundary at the left == exits the diamond
	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(bounds); ++ndx)
	{
		const bool startVertexInside =	LineRasterUtil::vertexOnLeftSideOfLine						(line.m_v0, LineRasterUtil::SubpixelLineSegment(bounds[ndx].p0, bounds[ndx].p1)) ||
										(bounds[ndx].edgeInclusive && LineRasterUtil::vertexOnLine	(line.m_v0, LineRasterUtil::SubpixelLineSegment(bounds[ndx].p0, bounds[ndx].p1)));
		const bool endVertexInside =	LineRasterUtil::vertexOnLeftSideOfLine						(line.m_v1, LineRasterUtil::SubpixelLineSegment(bounds[ndx].p0, bounds[ndx].p1)) ||
										(bounds[ndx].edgeInclusive && LineRasterUtil::vertexOnLine	(line.m_v1, LineRasterUtil::SubpixelLineSegment(bounds[ndx].p0, bounds[ndx].p1)));

		// start must be on inside this half space (left or at the inclusive boundary)
		if (!startVertexInside)
			continue;

		// end must be outside of this half-space (right or at non-inclusive boundary)
		if (endVertexInside)
			continue;

		// Does the line via v0 and v1 intersect the line segment p0-p1
		// <==> p0 and p1 are the different sides (LEFT, RIGHT) of the v0-v1 line.
		// Corners are not allowed, they are checked already
		LineRasterUtil::LINE_SIDE sideP0 = LineRasterUtil::getVertexSide(bounds[ndx].p0, line);
		LineRasterUtil::LINE_SIDE sideP1 = LineRasterUtil::getVertexSide(bounds[ndx].p1, line);

		if (sideP0 != LineRasterUtil::LINE_SIDE_INTERSECT &&
			sideP1 != LineRasterUtil::LINE_SIDE_INTERSECT &&
			sideP0 != sideP1)
			return true;
	}

	return false;
}

} // LineRasterUtil

TriangleRasterizer::TriangleRasterizer (const tcu::IVec4& viewport, const int numSamples, const RasterizationState& state)
	: m_viewport		(viewport)
	, m_numSamples		(numSamples)
	, m_winding			(state.winding)
	, m_horizontalFill	(state.horizontalFill)
	, m_verticalFill	(state.verticalFill)
	, m_face			(FACETYPE_LAST)
{
}

/*--------------------------------------------------------------------*//*!
 * \brief Initialize triangle rasterization
 * \param v0 Screen-space coordinates (x, y, z) and 1/w for vertex 0.
 * \param v1 Screen-space coordinates (x, y, z) and 1/w for vertex 1.
 * \param v2 Screen-space coordinates (x, y, z) and 1/w for vertex 2.
 *//*--------------------------------------------------------------------*/
void TriangleRasterizer::init (const tcu::Vec4& v0, const tcu::Vec4& v1, const tcu::Vec4& v2)
{
	m_v0 = v0;
	m_v1 = v1;
	m_v2 = v2;

	// Positions in fixed-point coordinates.
	const deInt64	x0		= toSubpixelCoord(v0.x());
	const deInt64	y0		= toSubpixelCoord(v0.y());
	const deInt64	x1		= toSubpixelCoord(v1.x());
	const deInt64	y1		= toSubpixelCoord(v1.y());
	const deInt64	x2		= toSubpixelCoord(v2.x());
	const deInt64	y2		= toSubpixelCoord(v2.y());

	// Initialize edge functions.
	if (m_winding == WINDING_CCW)
	{
		initEdgeCCW(m_edge01, m_horizontalFill, m_verticalFill, x0, y0, x1, y1);
		initEdgeCCW(m_edge12, m_horizontalFill, m_verticalFill, x1, y1, x2, y2);
		initEdgeCCW(m_edge20, m_horizontalFill, m_verticalFill, x2, y2, x0, y0);
	}
	else
	{
		// Reverse edges
		initEdgeCCW(m_edge01, m_horizontalFill, m_verticalFill, x1, y1, x0, y0);
		initEdgeCCW(m_edge12, m_horizontalFill, m_verticalFill, x2, y2, x1, y1);
		initEdgeCCW(m_edge20, m_horizontalFill, m_verticalFill, x0, y0, x2, y2);
	}

	// Determine face.
	const deInt64	s				= evaluateEdge(m_edge01, x2, y2);
	const bool		positiveArea	= (m_winding == WINDING_CCW) ? (s > 0) : (s < 0);
	m_face = positiveArea ? FACETYPE_FRONT : FACETYPE_BACK;

	if (!positiveArea)
	{
		// Reverse edges so that we can use CCW area tests & interpolation
		reverseEdge(m_edge01);
		reverseEdge(m_edge12);
		reverseEdge(m_edge20);
	}

	// Bounding box
	const deInt64	xMin	= de::min(de::min(x0, x1), x2);
	const deInt64	xMax	= de::max(de::max(x0, x1), x2);
	const deInt64	yMin	= de::min(de::min(y0, y1), y2);
	const deInt64	yMax	= de::max(de::max(y0, y1), y2);

	m_bboxMin.x() = floorSubpixelToPixelCoord	(xMin, m_horizontalFill	== FILL_LEFT);
	m_bboxMin.y() = floorSubpixelToPixelCoord	(yMin, m_verticalFill	== FILL_BOTTOM);
	m_bboxMax.x() = ceilSubpixelToPixelCoord	(xMax, m_horizontalFill	== FILL_RIGHT);
	m_bboxMax.y() = ceilSubpixelToPixelCoord	(yMax, m_verticalFill	== FILL_TOP);

	// Clamp to viewport
	const int		wX0		= m_viewport.x();
	const int		wY0		= m_viewport.y();
	const int		wX1		= wX0 + m_viewport.z() - 1;
	const int		wY1		= wY0 + m_viewport.w() -1;

	m_bboxMin.x() = de::clamp(m_bboxMin.x(), wX0, wX1);
	m_bboxMin.y() = de::clamp(m_bboxMin.y(), wY0, wY1);
	m_bboxMax.x() = de::clamp(m_bboxMax.x(), wX0, wX1);
	m_bboxMax.y() = de::clamp(m_bboxMax.y(), wY0, wY1);

	m_curPos = m_bboxMin;
}

void TriangleRasterizer::rasterizeSingleSample (FragmentPacket* const fragmentPackets, float* const depthValues, const int maxFragmentPackets, int& numPacketsRasterized)
{
	DE_ASSERT(maxFragmentPackets > 0);

	const deUint64	halfPixel	= 1ll << (RASTERIZER_SUBPIXEL_BITS-1);
	int				packetNdx	= 0;

	while (m_curPos.y() <= m_bboxMax.y() && packetNdx < maxFragmentPackets)
	{
		const int		x0		= m_curPos.x();
		const int		y0		= m_curPos.y();

		// Subpixel coords
		const deInt64	sx0		= toSubpixelCoord(x0)	+ halfPixel;
		const deInt64	sx1		= toSubpixelCoord(x0+1)	+ halfPixel;
		const deInt64	sy0		= toSubpixelCoord(y0)	+ halfPixel;
		const deInt64	sy1		= toSubpixelCoord(y0+1)	+ halfPixel;

		const deInt64	sx[4]	= { sx0, sx1, sx0, sx1 };
		const deInt64	sy[4]	= { sy0, sy0, sy1, sy1 };

		// Viewport test
		const bool		outX1	= x0+1 == m_viewport.x()+m_viewport.z();
		const bool		outY1	= y0+1 == m_viewport.y()+m_viewport.w();

		DE_ASSERT(x0 < m_viewport.x()+m_viewport.z());
		DE_ASSERT(y0 < m_viewport.y()+m_viewport.w());

		// Edge values
		tcu::Vector<deInt64, 4>	e01;
		tcu::Vector<deInt64, 4>	e12;
		tcu::Vector<deInt64, 4>	e20;

		// Coverage
		deUint64		coverage	= 0;

		// Evaluate edge values
		for (int i = 0; i < 4; i++)
		{
			e01[i] = evaluateEdge(m_edge01, sx[i], sy[i]);
			e12[i] = evaluateEdge(m_edge12, sx[i], sy[i]);
			e20[i] = evaluateEdge(m_edge20, sx[i], sy[i]);
		}

		// Compute coverage mask
		coverage = setCoverageValue(coverage, 1, 0, 0, 0,						isInsideCCW(m_edge01, e01[0]) && isInsideCCW(m_edge12, e12[0]) && isInsideCCW(m_edge20, e20[0]));
		coverage = setCoverageValue(coverage, 1, 1, 0, 0, !outX1 &&				isInsideCCW(m_edge01, e01[1]) && isInsideCCW(m_edge12, e12[1]) && isInsideCCW(m_edge20, e20[1]));
		coverage = setCoverageValue(coverage, 1, 0, 1, 0, !outY1 &&				isInsideCCW(m_edge01, e01[2]) && isInsideCCW(m_edge12, e12[2]) && isInsideCCW(m_edge20, e20[2]));
		coverage = setCoverageValue(coverage, 1, 1, 1, 0, !outX1 && !outY1 &&	isInsideCCW(m_edge01, e01[3]) && isInsideCCW(m_edge12, e12[3]) && isInsideCCW(m_edge20, e20[3]));

		// Advance to next location
		m_curPos.x() += 2;
		if (m_curPos.x() > m_bboxMax.x())
		{
			m_curPos.y() += 2;
			m_curPos.x()  = m_bboxMin.x();
		}

		if (coverage == 0)
			continue; // Discard.

		// Floating-point edge values for barycentrics etc.
		const tcu::Vec4		e01f	= e01.asFloat();
		const tcu::Vec4		e12f	= e12.asFloat();
		const tcu::Vec4		e20f	= e20.asFloat();

		// Compute depth values.
		if (depthValues)
		{
			const tcu::Vec4		ooSum	= 1.0f / (e01f + e12f + e20f);
			const tcu::Vec4		z0		= e12f * ooSum;
			const tcu::Vec4		z1		= e20f * ooSum;
			const tcu::Vec4		z2		= e01f * ooSum;

			depthValues[packetNdx*4+0] = z0[0]*m_v0.z() + z1[0]*m_v1.z() + z2[0]*m_v2.z();
			depthValues[packetNdx*4+1] = z0[1]*m_v0.z() + z1[1]*m_v1.z() + z2[1]*m_v2.z();
			depthValues[packetNdx*4+2] = z0[2]*m_v0.z() + z1[2]*m_v1.z() + z2[2]*m_v2.z();
			depthValues[packetNdx*4+3] = z0[3]*m_v0.z() + z1[3]*m_v1.z() + z2[3]*m_v2.z();
		}

		// Compute barycentrics and write out fragment packet
		{
			FragmentPacket& packet = fragmentPackets[packetNdx];

			const tcu::Vec4		b0		= e12f * m_v0.w();
			const tcu::Vec4		b1		= e20f * m_v1.w();
			const tcu::Vec4		b2		= e01f * m_v2.w();
			const tcu::Vec4		ooSum	= 1.0f / (b0 + b1 + b2);

			packet.position			= tcu::IVec2(x0, y0);
			packet.coverage			= coverage;
			packet.barycentric[0]	= b0 * ooSum;
			packet.barycentric[1]	= b1 * ooSum;
			packet.barycentric[2]	= 1.0f - packet.barycentric[0] - packet.barycentric[1];

			packetNdx += 1;
		}
	}

	DE_ASSERT(packetNdx <= maxFragmentPackets);
	numPacketsRasterized = packetNdx;
}

// Sample positions - ordered as (x, y) list.

// \note Macros are used to eliminate function calls even in debug builds.
#define SAMPLE_POS_TO_SUBPIXEL_COORD(POS)	\
	(deInt64)((POS) * (1<<RASTERIZER_SUBPIXEL_BITS) + 0.5f)

#define SAMPLE_POS(X, Y)	\
	SAMPLE_POS_TO_SUBPIXEL_COORD(X), SAMPLE_POS_TO_SUBPIXEL_COORD(Y)

static const deInt64 s_samplePos2[] =
{
	SAMPLE_POS(0.3f, 0.3f),
	SAMPLE_POS(0.7f, 0.7f)
};

static const deInt64 s_samplePos4[] =
{
	SAMPLE_POS(0.25f, 0.25f),
	SAMPLE_POS(0.75f, 0.25f),
	SAMPLE_POS(0.25f, 0.75f),
	SAMPLE_POS(0.75f, 0.75f)
};
DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_samplePos4) == 4*2);

static const deInt64 s_samplePos8[] =
{
	SAMPLE_POS( 7.f/16.f,  9.f/16.f),
	SAMPLE_POS( 9.f/16.f, 13.f/16.f),
	SAMPLE_POS(11.f/16.f,  3.f/16.f),
	SAMPLE_POS(13.f/16.f, 11.f/16.f),
	SAMPLE_POS( 1.f/16.f,  7.f/16.f),
	SAMPLE_POS( 5.f/16.f,  1.f/16.f),
	SAMPLE_POS(15.f/16.f,  5.f/16.f),
	SAMPLE_POS( 3.f/16.f, 15.f/16.f)
};
DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_samplePos8) == 8*2);

static const deInt64 s_samplePos16[] =
{
	SAMPLE_POS(1.f/8.f, 1.f/8.f),
	SAMPLE_POS(3.f/8.f, 1.f/8.f),
	SAMPLE_POS(5.f/8.f, 1.f/8.f),
	SAMPLE_POS(7.f/8.f, 1.f/8.f),
	SAMPLE_POS(1.f/8.f, 3.f/8.f),
	SAMPLE_POS(3.f/8.f, 3.f/8.f),
	SAMPLE_POS(5.f/8.f, 3.f/8.f),
	SAMPLE_POS(7.f/8.f, 3.f/8.f),
	SAMPLE_POS(1.f/8.f, 5.f/8.f),
	SAMPLE_POS(3.f/8.f, 5.f/8.f),
	SAMPLE_POS(5.f/8.f, 5.f/8.f),
	SAMPLE_POS(7.f/8.f, 5.f/8.f),
	SAMPLE_POS(1.f/8.f, 7.f/8.f),
	SAMPLE_POS(3.f/8.f, 7.f/8.f),
	SAMPLE_POS(5.f/8.f, 7.f/8.f),
	SAMPLE_POS(7.f/8.f, 7.f/8.f)
};
DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_samplePos16) == 16*2);

#undef SAMPLE_POS
#undef SAMPLE_POS_TO_SUBPIXEL_COORD

template<int NumSamples>
void TriangleRasterizer::rasterizeMultiSample (FragmentPacket* const fragmentPackets, float* const depthValues, const int maxFragmentPackets, int& numPacketsRasterized)
{
	DE_ASSERT(maxFragmentPackets > 0);

	const deInt64*	samplePos	= DE_NULL;
	const deUint64	halfPixel	= 1ll << (RASTERIZER_SUBPIXEL_BITS-1);
	int				packetNdx	= 0;

	switch (NumSamples)
	{
		case 2:		samplePos = s_samplePos2;	break;
		case 4:		samplePos = s_samplePos4;	break;
		case 8:		samplePos = s_samplePos8;	break;
		case 16:	samplePos = s_samplePos16;	break;
		default:
			DE_ASSERT(false);
	}

	while (m_curPos.y() <= m_bboxMax.y() && packetNdx < maxFragmentPackets)
	{
		const int		x0		= m_curPos.x();
		const int		y0		= m_curPos.y();

		// Base subpixel coords
		const deInt64	sx0		= toSubpixelCoord(x0);
		const deInt64	sx1		= toSubpixelCoord(x0+1);
		const deInt64	sy0		= toSubpixelCoord(y0);
		const deInt64	sy1		= toSubpixelCoord(y0+1);

		const deInt64	sx[4]	= { sx0, sx1, sx0, sx1 };
		const deInt64	sy[4]	= { sy0, sy0, sy1, sy1 };

		// Viewport test
		const bool		outX1	= x0+1 == m_viewport.x()+m_viewport.z();
		const bool		outY1	= y0+1 == m_viewport.y()+m_viewport.w();

		DE_ASSERT(x0 < m_viewport.x()+m_viewport.z());
		DE_ASSERT(y0 < m_viewport.y()+m_viewport.w());

		// Edge values
		tcu::Vector<deInt64, 4>	e01[NumSamples];
		tcu::Vector<deInt64, 4>	e12[NumSamples];
		tcu::Vector<deInt64, 4>	e20[NumSamples];

		// Coverage
		deUint64		coverage	= 0;

		// Evaluate edge values at sample positions
		for (int sampleNdx = 0; sampleNdx < NumSamples; sampleNdx++)
		{
			const deInt64 ox = samplePos[sampleNdx*2 + 0];
			const deInt64 oy = samplePos[sampleNdx*2 + 1];

			for (int fragNdx = 0; fragNdx < 4; fragNdx++)
			{
				e01[sampleNdx][fragNdx] = evaluateEdge(m_edge01, sx[fragNdx] + ox, sy[fragNdx] + oy);
				e12[sampleNdx][fragNdx] = evaluateEdge(m_edge12, sx[fragNdx] + ox, sy[fragNdx] + oy);
				e20[sampleNdx][fragNdx] = evaluateEdge(m_edge20, sx[fragNdx] + ox, sy[fragNdx] + oy);
			}
		}

		// Compute coverage mask
		for (int sampleNdx = 0; sampleNdx < NumSamples; sampleNdx++)
		{
			coverage = setCoverageValue(coverage, NumSamples, 0, 0, sampleNdx,						isInsideCCW(m_edge01, e01[sampleNdx][0]) && isInsideCCW(m_edge12, e12[sampleNdx][0]) && isInsideCCW(m_edge20, e20[sampleNdx][0]));
			coverage = setCoverageValue(coverage, NumSamples, 1, 0, sampleNdx, !outX1 &&			isInsideCCW(m_edge01, e01[sampleNdx][1]) && isInsideCCW(m_edge12, e12[sampleNdx][1]) && isInsideCCW(m_edge20, e20[sampleNdx][1]));
			coverage = setCoverageValue(coverage, NumSamples, 0, 1, sampleNdx, !outY1 &&			isInsideCCW(m_edge01, e01[sampleNdx][2]) && isInsideCCW(m_edge12, e12[sampleNdx][2]) && isInsideCCW(m_edge20, e20[sampleNdx][2]));
			coverage = setCoverageValue(coverage, NumSamples, 1, 1, sampleNdx, !outX1 && !outY1 &&	isInsideCCW(m_edge01, e01[sampleNdx][3]) && isInsideCCW(m_edge12, e12[sampleNdx][3]) && isInsideCCW(m_edge20, e20[sampleNdx][3]));
		}

		// Advance to next location
		m_curPos.x() += 2;
		if (m_curPos.x() > m_bboxMax.x())
		{
			m_curPos.y() += 2;
			m_curPos.x()  = m_bboxMin.x();
		}

		if (coverage == 0)
			continue; // Discard.

		// Compute depth values.
		if (depthValues)
		{
			for (int sampleNdx = 0; sampleNdx < NumSamples; sampleNdx++)
			{
				// Floating-point edge values at sample coordinates.
				const tcu::Vec4&	e01f	= e01[sampleNdx].asFloat();
				const tcu::Vec4&	e12f	= e12[sampleNdx].asFloat();
				const tcu::Vec4&	e20f	= e20[sampleNdx].asFloat();

				const tcu::Vec4		ooSum	= 1.0f / (e01f + e12f + e20f);
				const tcu::Vec4		z0		= e12f * ooSum;
				const tcu::Vec4		z1		= e20f * ooSum;
				const tcu::Vec4		z2		= e01f * ooSum;

				depthValues[(packetNdx*4+0)*NumSamples + sampleNdx] = z0[0]*m_v0.z() + z1[0]*m_v1.z() + z2[0]*m_v2.z();
				depthValues[(packetNdx*4+1)*NumSamples + sampleNdx] = z0[1]*m_v0.z() + z1[1]*m_v1.z() + z2[1]*m_v2.z();
				depthValues[(packetNdx*4+2)*NumSamples + sampleNdx] = z0[2]*m_v0.z() + z1[2]*m_v1.z() + z2[2]*m_v2.z();
				depthValues[(packetNdx*4+3)*NumSamples + sampleNdx] = z0[3]*m_v0.z() + z1[3]*m_v1.z() + z2[3]*m_v2.z();
			}
		}

		// Compute barycentrics and write out fragment packet
		{
			FragmentPacket& packet = fragmentPackets[packetNdx];

			// Floating-point edge values at pixel center.
			tcu::Vec4			e01f;
			tcu::Vec4			e12f;
			tcu::Vec4			e20f;

			for (int i = 0; i < 4; i++)
			{
				e01f[i] = float(evaluateEdge(m_edge01, sx[i] + halfPixel, sy[i] + halfPixel));
				e12f[i] = float(evaluateEdge(m_edge12, sx[i] + halfPixel, sy[i] + halfPixel));
				e20f[i] = float(evaluateEdge(m_edge20, sx[i] + halfPixel, sy[i] + halfPixel));
			}

			// Barycentrics & scale.
			const tcu::Vec4		b0		= e12f * m_v0.w();
			const tcu::Vec4		b1		= e20f * m_v1.w();
			const tcu::Vec4		b2		= e01f * m_v2.w();
			const tcu::Vec4		ooSum	= 1.0f / (b0 + b1 + b2);

			packet.position			= tcu::IVec2(x0, y0);
			packet.coverage			= coverage;
			packet.barycentric[0]	= b0 * ooSum;
			packet.barycentric[1]	= b1 * ooSum;
			packet.barycentric[2]	= 1.0f - packet.barycentric[0] - packet.barycentric[1];

			packetNdx += 1;
		}
	}

	DE_ASSERT(packetNdx <= maxFragmentPackets);
	numPacketsRasterized = packetNdx;
}

void TriangleRasterizer::rasterize (FragmentPacket* const fragmentPackets, float* const depthValues, const int maxFragmentPackets, int& numPacketsRasterized)
{
	DE_ASSERT(maxFragmentPackets > 0);

	switch (m_numSamples)
	{
		case 1:		rasterizeSingleSample		(fragmentPackets, depthValues, maxFragmentPackets, numPacketsRasterized);	break;
		case 2:		rasterizeMultiSample<2>		(fragmentPackets, depthValues, maxFragmentPackets, numPacketsRasterized);	break;
		case 4:		rasterizeMultiSample<4>		(fragmentPackets, depthValues, maxFragmentPackets, numPacketsRasterized);	break;
		case 8:		rasterizeMultiSample<8>		(fragmentPackets, depthValues, maxFragmentPackets, numPacketsRasterized);	break;
		case 16:	rasterizeMultiSample<16>	(fragmentPackets, depthValues, maxFragmentPackets, numPacketsRasterized);	break;
		default:
			DE_ASSERT(DE_FALSE);
	}
}

SingleSampleLineRasterizer::SingleSampleLineRasterizer (const tcu::IVec4& viewport)
	: m_viewport		(viewport)
	, m_curRowFragment	(0)
	, m_lineWidth		(0.0f)
{
}

SingleSampleLineRasterizer::~SingleSampleLineRasterizer	()
{
}

void SingleSampleLineRasterizer::init (const tcu::Vec4& v0, const tcu::Vec4& v1, float lineWidth)
{
	const bool						isXMajor		= de::abs((v1 - v0).x()) >= de::abs((v1 - v0).y());

	// Bounding box \note: with wide lines, the line is actually moved as in the spec
	const deInt32					lineWidthPixels	= (lineWidth > 1.0f) ? (deInt32)floor(lineWidth + 0.5f) : 1;

	const tcu::IVec2				minorDirection	= (isXMajor ? tcu::IVec2(0, 1) : tcu::IVec2(1, 0));
	const tcu::Vector<deInt64,2>	widthOffset		= (isXMajor ? tcu::Vector<deInt64,2>(0, -1) : tcu::Vector<deInt64,2>(-1, 0)) * (toSubpixelCoord(lineWidthPixels - 1) / 2);

	const deInt64					x0				= toSubpixelCoord(v0.x()) + widthOffset.x();
	const deInt64					y0				= toSubpixelCoord(v0.y()) + widthOffset.y();
	const deInt64					x1				= toSubpixelCoord(v1.x()) + widthOffset.x();
	const deInt64					y1				= toSubpixelCoord(v1.y()) + widthOffset.y();

	// line endpoints might be perturbed, add some margin
	const deInt64					xMin			= de::min(x0, x1) - toSubpixelCoord(1);
	const deInt64					xMax			= de::max(x0, x1) + toSubpixelCoord(1);
	const deInt64					yMin			= de::min(y0, y1) - toSubpixelCoord(1);
	const deInt64					yMax			= de::max(y0, y1) + toSubpixelCoord(1);

	// Remove invisible area

	if (isXMajor)
	{
		// clamp to viewport in major direction
		m_bboxMin.x() = de::clamp(floorSubpixelToPixelCoord(xMin, true), m_viewport.x(), m_viewport.x() + m_viewport.z() - 1);
		m_bboxMax.x() = de::clamp(ceilSubpixelToPixelCoord (xMax, true), m_viewport.x(), m_viewport.x() + m_viewport.z() - 1);

		// clamp to padded viewport in minor direction (wide lines might bleed over viewport in minor direction)
		m_bboxMin.y() = de::clamp(floorSubpixelToPixelCoord(yMin, true), m_viewport.y() - lineWidthPixels, m_viewport.y() + m_viewport.w() - 1);
		m_bboxMax.y() = de::clamp(ceilSubpixelToPixelCoord (yMax, true), m_viewport.y() - lineWidthPixels, m_viewport.y() + m_viewport.w() - 1);
	}
	else
	{
		// clamp to viewport in major direction
		m_bboxMin.y() = de::clamp(floorSubpixelToPixelCoord(yMin, true), m_viewport.y(), m_viewport.y() + m_viewport.w() - 1);
		m_bboxMax.y() = de::clamp(ceilSubpixelToPixelCoord (yMax, true), m_viewport.y(), m_viewport.y() + m_viewport.w() - 1);

		// clamp to padded viewport in minor direction (wide lines might bleed over viewport in minor direction)
		m_bboxMin.x() = de::clamp(floorSubpixelToPixelCoord(xMin, true), m_viewport.x() - lineWidthPixels, m_viewport.x() + m_viewport.z() - 1);
		m_bboxMax.x() = de::clamp(ceilSubpixelToPixelCoord (xMax, true), m_viewport.x() - lineWidthPixels, m_viewport.x() + m_viewport.z() - 1);
	}

	m_lineWidth = lineWidth;

	m_v0 = v0;
	m_v1 = v1;

	m_curPos = m_bboxMin;
	m_curRowFragment = 0;
}

void SingleSampleLineRasterizer::rasterize (FragmentPacket* const fragmentPackets, float* const depthValues, const int maxFragmentPackets, int& numPacketsRasterized)
{
	DE_ASSERT(maxFragmentPackets > 0);

	const deInt64								halfPixel		= 1ll << (RASTERIZER_SUBPIXEL_BITS-1);
	const deInt32								lineWidth		= (m_lineWidth > 1.0f) ? (deInt32)floor(m_lineWidth + 0.5f) : 1;
	const bool									isXMajor		= de::abs((m_v1 - m_v0).x()) >= de::abs((m_v1 - m_v0).y());
	const tcu::IVec2							minorDirection	= (isXMajor ? tcu::IVec2(0, 1) : tcu::IVec2(1, 0));
	const tcu::Vector<deInt64,2>				widthOffset		= (isXMajor ? tcu::Vector<deInt64,2>(0, -1) : tcu::Vector<deInt64,2>(-1, 0)) * (toSubpixelCoord(lineWidth - 1) / 2);
	const tcu::Vector<deInt64,2>				pa				= LineRasterUtil::toSubpixelVector(m_v0.xy()) + widthOffset;
	const tcu::Vector<deInt64,2>				pb				= LineRasterUtil::toSubpixelVector(m_v1.xy()) + widthOffset;
	const LineRasterUtil::SubpixelLineSegment	line			= LineRasterUtil::SubpixelLineSegment(pa, pb);

	int											packetNdx 		= 0;

	while (m_curPos.y() <= m_bboxMax.y() && packetNdx < maxFragmentPackets)
	{
		const tcu::Vector<deInt64,2> diamondPosition = LineRasterUtil::toSubpixelVector(m_curPos) + tcu::Vector<deInt64,2>(halfPixel,halfPixel);

		// Should current fragment be drawn? == does the segment exit this diamond?
		if (LineRasterUtil::doesLineSegmentExitDiamond(line, diamondPosition))
		{
			const tcu::Vector<deInt64,2> 	pr					= diamondPosition;
			const float 					t					= tcu::dot((pr - pa).asFloat(), (pb - pa).asFloat()) / tcu::lengthSquared(pb.asFloat() - pa.asFloat());

			// Rasterize on only fragments that are would end up in the viewport (i.e. visible)
			const int						minViewportLimit	= (isXMajor) ? (m_viewport.y())                  : (m_viewport.x());
			const int						maxViewportLimit	= (isXMajor) ? (m_viewport.y() + m_viewport.w()) : (m_viewport.x() + m_viewport.z());
			const int						fragmentLocation	= (isXMajor) ? (m_curPos.y())                    : (m_curPos.x());

			const int						rowFragBegin		= de::max(0, minViewportLimit - fragmentLocation);
			const int						rowFragEnd			= de::min(maxViewportLimit - fragmentLocation, lineWidth);

			// Wide lines require multiple fragments.
			for (; rowFragBegin + m_curRowFragment < rowFragEnd; m_curRowFragment++)
			{
				const tcu::IVec2 fragmentPos = m_curPos + minorDirection * (rowFragBegin + m_curRowFragment);

				// We only rasterize visible area
				DE_ASSERT(LineRasterUtil::inViewport(fragmentPos, m_viewport));

				// Compute depth values.
				if (depthValues)
				{
					const float za = m_v0.z();
					const float zb = m_v1.z();

					depthValues[packetNdx*4+0] = (1 - t) * za + t * zb;
					depthValues[packetNdx*4+1] = 0;
					depthValues[packetNdx*4+2] = 0;
					depthValues[packetNdx*4+3] = 0;
				}

				{
					// output this fragment
					// \note In order to make consistent output with multisampled line rasterization, output "barycentric" coordinates
					FragmentPacket& packet = fragmentPackets[packetNdx];

					const tcu::Vec4		b0		= tcu::Vec4(1 - t);
					const tcu::Vec4		b1		= tcu::Vec4(t);
					const tcu::Vec4		ooSum	= 1.0f / (b0 + b1);

					packet.position			= fragmentPos;
					packet.coverage			= getCoverageBit(1, 0, 0, 0);
					packet.barycentric[0]	= b0 * ooSum;
					packet.barycentric[1]	= b1 * ooSum;
					packet.barycentric[2]	= tcu::Vec4(0.0f);

					packetNdx += 1;
				}

				if (packetNdx == maxFragmentPackets)
				{
					m_curRowFragment++; // don't redraw this fragment again next time
					numPacketsRasterized = packetNdx;
					return;
				}
			}

			m_curRowFragment = 0;
		}

		++m_curPos.x();
		if (m_curPos.x() > m_bboxMax.x())
		{
			++m_curPos.y();
			m_curPos.x() = m_bboxMin.x();
		}
	}

	DE_ASSERT(packetNdx <= maxFragmentPackets);
	numPacketsRasterized = packetNdx;
}

MultiSampleLineRasterizer::MultiSampleLineRasterizer (const int numSamples, const tcu::IVec4& viewport)
	: m_numSamples			(numSamples)
	, m_triangleRasterizer0 (viewport, m_numSamples, RasterizationState())
	, m_triangleRasterizer1 (viewport, m_numSamples, RasterizationState())
{
}

MultiSampleLineRasterizer::~MultiSampleLineRasterizer ()
{
}

void MultiSampleLineRasterizer::init (const tcu::Vec4& v0, const tcu::Vec4& v1, float lineWidth)
{
	// allow creation of single sampled rasterizer objects but do not allow using them
	DE_ASSERT(m_numSamples > 1);

	const tcu::Vec2 lineVec		= tcu::Vec2(tcu::Vec4(v1).xy()) - tcu::Vec2(tcu::Vec4(v0).xy());
	const tcu::Vec2 normal2		= tcu::normalize(tcu::Vec2(-lineVec[1], lineVec[0]));
	const tcu::Vec4 normal4		= tcu::Vec4(normal2.x(), normal2.y(), 0, 0);
	const float offset			= lineWidth / 2.0f;

	const tcu::Vec4 p0 = v0 + normal4 * offset;
	const tcu::Vec4 p1 = v0 - normal4 * offset;
	const tcu::Vec4 p2 = v1 - normal4 * offset;
	const tcu::Vec4 p3 = v1 + normal4 * offset;

	// Edge 0 -> 1 is always along the line and edge 1 -> 2 is in 90 degree angle to the line
	m_triangleRasterizer0.init(p0, p3, p2);
	m_triangleRasterizer1.init(p2, p1, p0);
}

void MultiSampleLineRasterizer::rasterize (FragmentPacket* const fragmentPackets, float* const depthValues, const int maxFragmentPackets, int& numPacketsRasterized)
{
	DE_ASSERT(maxFragmentPackets > 0);

	m_triangleRasterizer0.rasterize(fragmentPackets, depthValues, maxFragmentPackets, numPacketsRasterized);

	// Remove 3rd barycentric value and rebalance. Lines do not have non-zero barycentric at index 2
	for (int packNdx = 0; packNdx < numPacketsRasterized; ++packNdx)
	for (int fragNdx = 0; fragNdx < 4; fragNdx++)
	{
		float removedValue = fragmentPackets[packNdx].barycentric[2][fragNdx];
		fragmentPackets[packNdx].barycentric[2][fragNdx] = 0.0f;
		fragmentPackets[packNdx].barycentric[1][fragNdx] += removedValue;
	}

	// rasterizer 0 filled the whole buffer?
	if (numPacketsRasterized == maxFragmentPackets)
		return;

	{
		FragmentPacket* const nextFragmentPackets	= fragmentPackets + numPacketsRasterized;
		float* nextDepthValues						= (depthValues) ? (depthValues+4*numPacketsRasterized*m_numSamples) : (DE_NULL);
		int numPacketsRasterized2					= 0;

		m_triangleRasterizer1.rasterize(nextFragmentPackets, nextDepthValues, maxFragmentPackets - numPacketsRasterized, numPacketsRasterized2);

		numPacketsRasterized += numPacketsRasterized2;

		// Fix swapped barycentrics in the second triangle
		for (int packNdx = 0; packNdx < numPacketsRasterized2; ++packNdx)
		for (int fragNdx = 0; fragNdx < 4; fragNdx++)
		{
			float removedValue = nextFragmentPackets[packNdx].barycentric[2][fragNdx];
			nextFragmentPackets[packNdx].barycentric[2][fragNdx] = 0.0f;
			nextFragmentPackets[packNdx].barycentric[1][fragNdx] += removedValue;

			// edge has reversed direction
			std::swap(nextFragmentPackets[packNdx].barycentric[0][fragNdx], nextFragmentPackets[packNdx].barycentric[1][fragNdx]);
		}
	}
}

} // rr