Allow correct and projected interpolation equations in single-sampled line interpolat...
authorJarkko Pöyry <jpoyry@google.com>
Fri, 20 Feb 2015 03:40:03 +0000 (19:40 -0800)
committerJarkko Pöyry <jpoyry@google.com>
Mon, 9 Mar 2015 23:07:01 +0000 (16:07 -0700)
- Use proper interpolation equation for verification. Do not
  approximate distance to pixel center by projecting it along major
  direction. (i.e. just selecting major compontent).
- Detect projected interpolation in interpolation tests and issue
  a QualityWarning for such implementations.
- Calculate line interpolation weights in screen space, not in clip
  space.

Bug: 19410338
Change-Id: Ie27aa9cf121d1c92c86cda19a7b8c97e132fcb66

framework/referencerenderer/rrRasterizer.cpp
framework/referencerenderer/rrRasterizer.hpp
modules/gles2/functional/es2fRasterizationTests.cpp
modules/gles3/functional/es3fRasterizationTests.cpp
modules/glshared/glsRasterizationTestUtil.cpp
modules/glshared/glsRasterizationTestUtil.hpp

index 53d3ecc..df5b412 100644 (file)
@@ -865,7 +865,7 @@ SingleSampleLineRasterizer::SingleSampleLineRasterizer (const tcu::IVec4& viewpo
 {
 }
 
-SingleSampleLineRasterizer::~SingleSampleLineRasterizer        ()
+SingleSampleLineRasterizer::~SingleSampleLineRasterizer (void)
 {
 }
 
@@ -925,16 +925,18 @@ void SingleSampleLineRasterizer::rasterize (FragmentPacket* const fragmentPacket
 {
        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);
+       const deInt64                                                           halfPixel                       = 1ll << (RASTERIZER_SUBPIXEL_BITS-1);
+       const deInt32                                                           lineWidth                       = (m_lineWidth > 1.0f) ? deFloorFloatToInt32(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 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 tcu::Vector<deInt64,2>                            widthOffset                     = -minorDirection.cast<deInt64>() * (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;
+       int                                                                                     packetNdx                       = 0;
 
        while (m_curPos.y() <= m_bboxMax.y() && packetNdx < maxFragmentPackets)
        {
@@ -943,21 +945,19 @@ void SingleSampleLineRasterizer::rasterize (FragmentPacket* const fragmentPacket
                // 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());
+                       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                                               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);
+                               const int                       replicationId   = rowFragBegin + m_curRowFragment;
+                               const tcu::IVec2        fragmentPos             = m_curPos + minorDirection * replicationId;
 
                                // We only rasterize visible area
                                DE_ASSERT(LineRasterUtil::inViewport(fragmentPos, m_viewport));
@@ -1088,4 +1088,70 @@ void MultiSampleLineRasterizer::rasterize (FragmentPacket* const fragmentPackets
        }
 }
 
+LineExitDiamondGenerator::LineExitDiamondGenerator (void)
+{
+}
+
+LineExitDiamondGenerator::~LineExitDiamondGenerator (void)
+{
+}
+
+void LineExitDiamondGenerator::init (const tcu::Vec4& v0, const tcu::Vec4& v1)
+{
+       const deInt64                                   x0                              = toSubpixelCoord(v0.x());
+       const deInt64                                   y0                              = toSubpixelCoord(v0.y());
+       const deInt64                                   x1                              = toSubpixelCoord(v1.x());
+       const deInt64                                   y1                              = toSubpixelCoord(v1.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);
+
+       m_bboxMin.x() = floorSubpixelToPixelCoord(xMin, true);
+       m_bboxMin.y() = floorSubpixelToPixelCoord(yMin, true);
+       m_bboxMax.x() = ceilSubpixelToPixelCoord (xMax, true);
+       m_bboxMax.y() = ceilSubpixelToPixelCoord (yMax, true);
+
+       m_v0 = v0;
+       m_v1 = v1;
+
+       m_curPos = m_bboxMin;
+}
+
+void LineExitDiamondGenerator::rasterize (LineExitDiamond* const lineDiamonds, const int maxDiamonds, int& numWritten)
+{
+       DE_ASSERT(maxDiamonds > 0);
+
+       const deInt64                                                           halfPixel                       = 1ll << (RASTERIZER_SUBPIXEL_BITS-1);
+       const tcu::Vector<deInt64,2>                            pa                                      = LineRasterUtil::toSubpixelVector(m_v0.xy());
+       const tcu::Vector<deInt64,2>                            pb                                      = LineRasterUtil::toSubpixelVector(m_v1.xy());
+       const LineRasterUtil::SubpixelLineSegment       line                            = LineRasterUtil::SubpixelLineSegment(pa, pb);
+
+       int                                                                                     diamondNdx                      = 0;
+
+       while (m_curPos.y() <= m_bboxMax.y() && diamondNdx < maxDiamonds)
+       {
+               const tcu::Vector<deInt64,2> diamondPosition = LineRasterUtil::toSubpixelVector(m_curPos) + tcu::Vector<deInt64,2>(halfPixel,halfPixel);
+
+               if (LineRasterUtil::doesLineSegmentExitDiamond(line, diamondPosition))
+               {
+                       LineExitDiamond& packet = lineDiamonds[diamondNdx];
+                       packet.position = m_curPos;
+                       ++diamondNdx;
+               }
+
+               ++m_curPos.x();
+               if (m_curPos.x() > m_bboxMax.x())
+               {
+                       ++m_curPos.y();
+                       m_curPos.x() = m_bboxMin.x();
+               }
+       }
+
+       DE_ASSERT(diamondNdx <= maxDiamonds);
+       numWritten = diamondNdx;
+}
+
 } // rr
index fabd7af..f1ccd98 100644 (file)
@@ -161,10 +161,11 @@ private:
        tcu::IVec2                              m_curPos;               //!< Current rasterization position.
 };
 
+
 /*--------------------------------------------------------------------*//*!
  * \brief Single sample line rasterizer
  *
- * Triangle rasterizer implements following features:
+ * Line rasterizer implements following features:
  *  - Rasterization using fixed-point coordinates
  *  - Depth interpolation
  *  - Perspective-correct interpolation
@@ -177,7 +178,7 @@ class SingleSampleLineRasterizer
 {
 public:
                                                                        SingleSampleLineRasterizer      (const tcu::IVec4& viewport);
-                                                                       ~SingleSampleLineRasterizer     ();
+                                                                       ~SingleSampleLineRasterizer     (void);
 
        void                                                    init                                            (const tcu::Vec4& v0, const tcu::Vec4& v1, float lineWidth);
 
@@ -205,7 +206,7 @@ private:
 /*--------------------------------------------------------------------*//*!
  * \brief Multisampled line rasterizer
  *
- * Triangle rasterizer implements following features:
+ * Line rasterizer implements following features:
  *  - Rasterization using fixed-point coordinates
  *  - Depth interpolation
  *  - Perspective-correct interpolation
@@ -237,6 +238,49 @@ private:
        TriangleRasterizer                      m_triangleRasterizer1;
 };
 
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Pixel diamond
+ *
+ * Structure representing a diamond a line exits.
+ *//*--------------------------------------------------------------------*/
+struct LineExitDiamond
+{
+       tcu::IVec2      position;
+};
+
+/*--------------------------------------------------------------------*//*!
+ * \brief Line exit diamond generator
+ *
+ * For a given line, generates list of diamonds the line exits using the
+ * line-exit rules of the line rasterization. Does not do scissoring.
+ *
+ * \note Not used by rr, but provided to prevent test cases requiring
+ *       accurate diamonds from abusing SingleSampleLineRasterizer.
+ *//*--------------------------------------------------------------------*/
+class LineExitDiamondGenerator
+{
+public:
+                                                                       LineExitDiamondGenerator        (void);
+                                                                       ~LineExitDiamondGenerator       (void);
+
+       void                                                    init                                            (const tcu::Vec4& v0, const tcu::Vec4& v1);
+
+       // only available after init()
+       void                                                    rasterize                                       (LineExitDiamond* const lineDiamonds, const int maxDiamonds, int& numWritten);
+
+private:
+                                                                       LineExitDiamondGenerator        (const LineExitDiamondGenerator&); // not allowed
+       LineExitDiamondGenerator&               operator=                                       (const LineExitDiamondGenerator&); // not allowed
+
+       // Per-line rasterization state.
+       tcu::Vec4                                               m_v0;
+       tcu::Vec4                                               m_v1;
+       tcu::IVec2                                              m_bboxMin;                      //!< Bounding box min (inclusive).
+       tcu::IVec2                                              m_bboxMax;                      //!< Bounding box max (inclusive).
+       tcu::IVec2                                              m_curPos;                       //!< Current rasterization position.
+};
+
 } // rr
 
 #endif // _RRRASTERIZER_HPP
index c78aad1..5d46b05 100644 (file)
@@ -27,6 +27,7 @@
 #include "tcuRenderTarget.hpp"
 #include "tcuVectorUtil.hpp"
 #include "tcuStringTemplate.hpp"
+#include "tcuResultCollector.hpp"
 #include "gluShaderProgram.hpp"
 #include "gluRenderContext.hpp"
 #include "gluPixelTransfer.hpp"
@@ -1583,20 +1584,20 @@ void TriangleInterpolationTest::extractTriangles (std::vector<TriangleSceneSpec:
 class LineInterpolationTest : public BaseRenderingCase
 {
 public:
-                                               LineInterpolationTest   (Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, float lineWidth);
-                                               ~LineInterpolationTest  (void);
-       IterateResult           iterate                                 (void);
+                                                       LineInterpolationTest   (Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, float lineWidth);
+                                                       ~LineInterpolationTest  (void);
+       IterateResult                   iterate                                 (void);
 
 private:
-       void                            generateVertices                (int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const;
-       void                            extractLines                    (std::vector<LineSceneSpec::SceneLine>& outLines, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const;
+       void                                    generateVertices                (int iteration, std::vector<tcu::Vec4>& outVertices, std::vector<tcu::Vec4>& outColors) const;
+       void                                    extractLines                    (std::vector<LineSceneSpec::SceneLine>& outLines, const std::vector<tcu::Vec4>& vertices, const std::vector<tcu::Vec4>& colors) const;
 
-       const glw::GLenum       m_primitive;
-       const bool                      m_projective;
-       const int                       m_iterationCount;
+       const glw::GLenum               m_primitive;
+       const bool                              m_projective;
+       const int                               m_iterationCount;
 
-       int                                     m_iteration;
-       bool                            m_allIterationsPassed;
+       int                                             m_iteration;
+       tcu::ResultCollector    m_result;
 };
 
 LineInterpolationTest::LineInterpolationTest (Context& ctx, const char* name, const char* desc, glw::GLenum primitive, int flags, float lineWidth)
@@ -1605,7 +1606,6 @@ LineInterpolationTest::LineInterpolationTest (Context& ctx, const char* name, co
        , m_projective                  ((flags & INTERPOLATIONFLAGS_PROJECTED) != 0)
        , m_iterationCount              (3)
        , m_iteration                   (0)
-       , m_allIterationsPassed (true)
 {
        m_lineWidth = lineWidth;
 }
@@ -1642,6 +1642,7 @@ LineInterpolationTest::IterateResult LineInterpolationTest::iterate (void)
        {
                RasterizationArguments  args;
                LineSceneSpec                   scene;
+               LineInterpolationMethod iterationResult;
 
                args.numSamples         = m_numSamples;
                args.subpixelBits       = m_subpixelBits;
@@ -1652,18 +1653,38 @@ LineInterpolationTest::IterateResult LineInterpolationTest::iterate (void)
                scene.lines.swap(lines);
                scene.lineWidth = m_lineWidth;
 
-               if (!verifyLineGroupInterpolation(resultImage, scene, args, m_testCtx.getLog()))
-                       m_allIterationsPassed = false;
+               iterationResult = verifyLineGroupInterpolation(resultImage, scene, args, m_testCtx.getLog());
+               switch (iterationResult)
+               {
+                       case LINEINTERPOLATION_STRICTLY_CORRECT:
+                               // line interpolation matches the specification
+                               m_result.addResult(QP_TEST_RESULT_PASS, "Pass");
+                               break;
+
+                       case LINEINTERPOLATION_PROJECTED:
+                               // line interpolation weights are otherwise correct, but they are projected onto major axis
+                               m_testCtx.getLog()      << tcu::TestLog::Message
+                                                                       << "Interpolation was calculated using coordinates projected onto major axis. "
+                                                                          "This method does not produce the same values as the non-projecting method defined in the specification."
+                                                                       << tcu::TestLog::EndMessage;
+                               m_result.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Interpolation was calculated using projected coordinateds");
+                               break;
+
+                       case LINEINTERPOLATION_INCORRECT:
+                               // line interpolation is incorrect
+                               m_result.addResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
+                               break;
+
+                       default:
+                               DE_ASSERT(false);
+                               break;
+               }
        }
 
        // result
        if (++m_iteration == m_iterationCount)
        {
-               if (m_allIterationsPassed)
-                       m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
-               else
-                       m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
-
+               m_result.setTestContextResult(m_testCtx);
                return STOP;
        }
        else
index d32f455..fa3f6b9 100644 (file)
@@ -28,6 +28,7 @@
 #include "tcuVectorUtil.hpp"
 #include "tcuStringTemplate.hpp"
 #include "tcuTextureUtil.hpp"
+#include "tcuResultCollector.hpp"
 #include "gluShaderProgram.hpp"
 #include "gluRenderContext.hpp"
 #include "gluPixelTransfer.hpp"
@@ -1905,7 +1906,7 @@ private:
        const PrimitiveWideness m_primitiveWideness;
 
        int                                             m_iteration;
-       bool                                    m_allIterationsPassed;
+       tcu::ResultCollector    m_result;
        float                                   m_maxLineWidth;
        std::vector<float>              m_lineWidths;
 };
@@ -1917,7 +1918,6 @@ LineInterpolationTest::LineInterpolationTest (Context& ctx, const char* name, co
        , m_iterationCount              (3)
        , m_primitiveWideness   (wideness)
        , m_iteration                   (0)
-       , m_allIterationsPassed (true)
        , m_maxLineWidth                (1.0f)
 {
        m_flatshade = ((flags & INTERPOLATIONFLAGS_FLATSHADE) != 0);
@@ -1992,6 +1992,7 @@ LineInterpolationTest::IterateResult LineInterpolationTest::iterate (void)
                {
                        RasterizationArguments  args;
                        LineSceneSpec                   scene;
+                       LineInterpolationMethod iterationResult;
 
                        args.numSamples         = m_numSamples;
                        args.subpixelBits       = m_subpixelBits;
@@ -2002,8 +2003,32 @@ LineInterpolationTest::IterateResult LineInterpolationTest::iterate (void)
                        scene.lines.swap(lines);
                        scene.lineWidth = getLineWidth();
 
-                       if (!verifyLineGroupInterpolation(resultImage, scene, args, m_testCtx.getLog()))
-                               m_allIterationsPassed = false;
+                       iterationResult = verifyLineGroupInterpolation(resultImage, scene, args, m_testCtx.getLog());
+                       switch (iterationResult)
+                       {
+                               case LINEINTERPOLATION_STRICTLY_CORRECT:
+                                       // line interpolation matches the specification
+                                       m_result.addResult(QP_TEST_RESULT_PASS, "Pass");
+                                       break;
+
+                               case LINEINTERPOLATION_PROJECTED:
+                                       // line interpolation weights are otherwise correct, but they are projected onto major axis
+                                       m_testCtx.getLog()      << tcu::TestLog::Message
+                                                                               << "Interpolation was calculated using coordinates projected onto major axis. "
+                                                                               "This method does not produce the same values as the non-projecting method defined in the specification."
+                                                                               << tcu::TestLog::EndMessage;
+                                       m_result.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Interpolation was calculated using projected coordinateds");
+                                       break;
+
+                               case LINEINTERPOLATION_INCORRECT:
+                                       // line interpolation is incorrect
+                                       m_result.addResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
+                                       break;
+
+                               default:
+                                       DE_ASSERT(false);
+                                       break;
+                       }
                }
        }
        else
@@ -2012,11 +2037,7 @@ LineInterpolationTest::IterateResult LineInterpolationTest::iterate (void)
        // result
        if (++m_iteration == m_iterationCount)
        {
-               if (m_allIterationsPassed)
-                       m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
-               else
-                       m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Found invalid pixel values");
-
+               m_result.setTestContextResult(m_testCtx);
                return STOP;
        }
        else
index f479d5e..d50a8c8 100644 (file)
@@ -316,6 +316,19 @@ float minimalRangeDivision (float minDividend, float maxDividend, float minDivis
        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;
@@ -389,42 +402,43 @@ InterpolationRange calcTriangleInterpolationWeights (const tcu::Vec4& p0, const
        return returnValue;
 }
 
-LineInterpolationRange calcSingleSampleLineInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec2& ndpoint)
+LineInterpolationRange calcLineInterpolationWeights (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr)
 {
-       const int divError = 3;
-
-       const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
-       const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
-
-       // project p to the line along the minor direction
+       const int roundError    = 1;
+       const int divError              = 3;
 
-       const bool              xMajor          = (de::abs(nd0.x() - nd1.x()) >= de::abs(nd0.y() - nd1.y()));
-       const tcu::Vec2 minorDir        = (xMajor) ? (tcu::Vec2(0.0f, 1.0f)) : (tcu::Vec2(1.0f, 0.0f));
-       const tcu::Vec2 lineDir         (nd1 - nd0);
-       const tcu::Vec2 d                       (ndpoint - nd0);
+       // calc weights:
+       //                      (1-t) / wa                                      t / wb
+       //              -------------------     ,       -------------------
+       //              (1-t) / wa + t / wb             (1-t) / wa + t / wb
 
-       // calculate factors: vec2((1-t) / p0.w, t / p1.w) / ((1-t) / p0.w + t / p1.w)
+       // 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);
 
-       const float             tFactorMax                              = getMaxValueWithinError(-(1.0f / (minorDir.x()*lineDir.y() - lineDir.x()*minorDir.y())), divError);
-       const float             tFactorMin                              = getMinValueWithinError(-(1.0f / (minorDir.x()*lineDir.y() - lineDir.x()*minorDir.y())), divError);
-       DE_ASSERT(tFactorMin <= tFactorMax);
+       // 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);
 
-       const float             tResult1                                = tFactorMax * (minorDir.y()*d.x() - minorDir.x()*d.y());
-       const float             tResult2                                = tFactorMin * (minorDir.y()*d.x() - minorDir.x()*d.y());
-       const float             tMax                                    = de::max(tResult1, tResult2);
-       const float             tMin                                    = de::min(tResult1, tResult2);
+       // 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, p1.w(), p1.w()), divError);
-       const float             perspectiveTMin                 = getMinValueWithinError(minimalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError);
+       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), p0.w(), p0.w()), divError);
-       const float             perspectiveInvTMin              = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError);
+       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   = perspectiveTMax + perspectiveInvTMax;
-       const float             perspectiveDivisorMin   = perspectiveTMin + perspectiveInvTMin;
+       const float             perspectiveDivisorMax   = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
+       const float             perspectiveDivisorMin   = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
        DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
 
        LineInterpolationRange returnValue;
@@ -439,26 +453,33 @@ LineInterpolationRange calcSingleSampleLineInterpolationWeights (const tcu::Vec4
        return returnValue;
 }
 
-LineInterpolationRange calcMultiSampleLineInterpolationWeights (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec2& ndpoint)
+LineInterpolationRange calcLineInterpolationWeightsAxisProjected (const tcu::Vec2& pa, float wa, const tcu::Vec2& pb, float wb, const tcu::Vec2& pr)
 {
-       const int divError = 3;
+       const int       roundError              = 1;
+       const int       divError                = 3;
+       const bool      isXMajor                = isLineXMajor(pa, pb);
+       const int       majorAxisNdx    = (isXMajor) ? (0) : (1);
 
-       // calc weights: vec2((1-t) / p0.w, t / p1.w) / ((1-t) / p0.w + t / p1.w)
+       // calc weights:
+       //                      (1-t) / wa                                      t / wb
+       //              -------------------     ,       -------------------
+       //              (1-t) / wa + t / wb             (1-t) / wa + t / wb
 
-       // highp vertex shader
-       const tcu::Vec2 nd0 = p0.swizzle(0, 1) / p0.w();
-       const tcu::Vec2 nd1 = p1.swizzle(0, 1) / p1.w();
+       // 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        = tcu::dot(ndpoint - nd0, nd1 - nd0);
+       const float             dividend        = (pr[majorAxisNdx] - pa[majorAxisNdx]);
        const float             dividendMax     = getMaxValueWithinError(dividend, 1);
-       const float             dividendMin     = 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(nd1 - nd0);
+       // Allow 1 ULP
+       const float             divisor         = (pb[majorAxisNdx] - pa[majorAxisNdx]);
        const float             divisorMax      = getMaxValueWithinError(divisor, 1);
-       const float             divisorMin      = getMaxValueWithinError(divisor, 1);
+       const float             divisorMin      = getMinValueWithinError(divisor, 1);
        DE_ASSERT(divisorMin <= divisorMax);
 
        // Allow 3 ULP precision for division
@@ -466,16 +487,16 @@ LineInterpolationRange calcMultiSampleLineInterpolationWeights (const tcu::Vec4&
        const float             tMin            = getMinValueWithinError(minimalRangeDivision(dividendMin, dividendMax, divisorMin, divisorMax), divError);
        DE_ASSERT(tMin <= tMax);
 
-       const float             perspectiveTMax                 = getMaxValueWithinError(maximalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError);
-       const float             perspectiveTMin                 = getMinValueWithinError(minimalRangeDivision(tMin, tMax, p1.w(), p1.w()), divError);
+       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), p0.w(), p0.w()), divError);
-       const float             perspectiveInvTMin              = getMinValueWithinError(minimalRangeDivision((1.0f - tMax), (1.0f - tMin), p0.w(), p0.w()), divError);
+       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   = perspectiveTMax + perspectiveInvTMax;
-       const float             perspectiveDivisorMin   = perspectiveTMin + perspectiveInvTMin;
+       const float             perspectiveDivisorMax   = getMaxValueWithinError(perspectiveTMax + perspectiveInvTMax, roundError);
+       const float             perspectiveDivisorMin   = getMinValueWithinError(perspectiveTMin + perspectiveInvTMin, roundError);
        DE_ASSERT(perspectiveDivisorMin <= perspectiveDivisorMax);
 
        LineInterpolationRange returnValue;
@@ -490,38 +511,55 @@ LineInterpolationRange calcMultiSampleLineInterpolationWeights (const tcu::Vec4&
        return returnValue;
 }
 
-LineInterpolationRange calcSingleSampleLineInterpolationRange (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::IVec2& pixel, const tcu::IVec2& viewportSize, int subpixelBits)
+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 / (1UL << subpixelBits));
        const float testSquarePos  = (0.5f - testSquareSize / 2);
+
        const tcu::Vec2 corners[4] =
        {
-               tcu::Vec2((pixel.x() + testSquarePos + 0.0f)           / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f          ) / viewportSize.y() * 2.0f - 1.0f),
-               tcu::Vec2((pixel.x() + testSquarePos + 0.0f)           / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f),
-               tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + testSquareSize) / viewportSize.y() * 2.0f - 1.0f),
-               tcu::Vec2((pixel.x() + testSquarePos + testSquareSize) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + testSquarePos + 0.0f          ) / viewportSize.y() * 2.0f - 1.0f),
+               tcu::Vec2(pixel.x() + testSquarePos + 0.0f,                             pixel.y() + testSquarePos + 0.0f),
+               tcu::Vec2(pixel.x() + testSquarePos + 0.0f,                             pixel.y() + testSquarePos + testSquareSize),
+               tcu::Vec2(pixel.x() + testSquarePos + testSquareSize,   pixel.y() + testSquarePos + testSquareSize),
+               tcu::Vec2(pixel.x() + testSquarePos + testSquareSize,   pixel.y() + testSquarePos + 0.0f),
        };
 
        // calculate interpolation as a line
        const LineInterpolationRange weights[4] =
        {
-               calcSingleSampleLineInterpolationWeights(p0, p1, corners[0]),
-               calcSingleSampleLineInterpolationWeights(p0, p1, corners[1]),
-               calcSingleSampleLineInterpolationWeights(p0, p1, corners[2]),
-               calcSingleSampleLineInterpolationWeights(p0, p1, corners[3]),
+               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));
 
-       // 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
        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;
@@ -584,19 +622,26 @@ struct MultisampleLineInterpolator
                // allow interpolation weights anywhere in the pixel
                const tcu::Vec2 corners[4] =
                {
-                       tcu::Vec2((pixel.x() + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 0.0f) / viewportSize.y() * 2.0f - 1.0f),
-                       tcu::Vec2((pixel.x() + 0.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 1.0f) / viewportSize.y() * 2.0f - 1.0f),
-                       tcu::Vec2((pixel.x() + 1.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 1.0f) / viewportSize.y() * 2.0f - 1.0f),
-                       tcu::Vec2((pixel.x() + 1.0f) / viewportSize.x() * 2.0f - 1.0f, (pixel.y() + 0.0f) / viewportSize.y() * 2.0f - 1.0f),
+                       tcu::Vec2(pixel.x() + 0.0f, pixel.y() + 0.0f),
+                       tcu::Vec2(pixel.x() + 0.0f, pixel.y() + 1.0f),
+                       tcu::Vec2(pixel.x() + 1.0f, pixel.y() + 1.0f),
+                       tcu::Vec2(pixel.x() + 1.0f, 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 * viewportSize.x(),
+                                                                          (scene.lines[lineNdx].positions[0].y() / wa + 1.0f) * 0.5f * viewportSize.y());
+               const tcu::Vec2 pb = tcu::Vec2((scene.lines[lineNdx].positions[1].x() / wb + 1.0f) * 0.5f * viewportSize.x(),
+                                                                          (scene.lines[lineNdx].positions[1].y() / wb + 1.0f) * 0.5f * viewportSize.y());
+
                // calculate interpolation as a line
                const LineInterpolationRange weights[4] =
                {
-                       calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[0]),
-                       calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[1]),
-                       calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[2]),
-                       calcMultiSampleLineInterpolationWeights(scene.lines[lineNdx].positions[0], scene.lines[lineNdx].positions[1], corners[3]),
+                       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));
@@ -978,42 +1023,62 @@ bool verifyMultisamplePointGroupRasterization (const tcu::Surface& surface, cons
        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());
+       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()));
-
-               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::Vec4 lineScreenSpace[2] =
-               {
-                       tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
-                       tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
-               };
-
-               rasterizer.init(lineScreenSpace[0], lineScreenSpace[1], scene.lineWidth);
+               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] = de::abs(lineScreenSpace[1].x() - lineScreenSpace[0].x()) >= de::abs(lineScreenSpace[1].y() - lineScreenSpace[0].y());
+               lineIsXMajor[lineNdx] = isPackedSSLineXMajor(screenspaceLines[lineNdx]);
 
                for (;;)
                {
@@ -1352,7 +1417,7 @@ bool verifySinglesampleLineGroupRasterization (const tcu::Surface& surface, cons
        return allOK;
 }
 
-struct SingleSampleLineCoverageCandidate
+struct SingleSampleNarrowLineCandidate
 {
        int                     lineNdx;
        tcu::IVec3      colorMin;
@@ -1363,76 +1428,92 @@ struct SingleSampleLineCoverageCandidate
        tcu::Vec3       valueRangeMax;
 };
 
-bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+void setMaskMapCoverageBitForLine (int bitNdx, const tcu::Vec2& screenSpaceP0, const tcu::Vec2& screenSpaceP1, float lineWidth, tcu::PixelBufferAccess maskMap)
 {
-       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
+       {
+               MAX_PACKETS = 32,
+       };
 
-       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;
+       rr::SingleSampleLineRasterizer  rasterizer                              (tcu::IVec4(0, 0, maskMap.getWidth(), maskMap.getHeight()));
+       int                                                             numRasterized                   = MAX_PACKETS;
+       rr::FragmentPacket                              packets[MAX_PACKETS];
 
-       // log format
+       rasterizer.init(tcu::Vec4(screenSpaceP0.x(), screenSpaceP0.y(), 0.0f, 1.0f),
+                                       tcu::Vec4(screenSpaceP1.x(), screenSpaceP1.y(), 0.0f, 1.0f),
+                                       lineWidth);
 
-       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;
+       while (numRasterized == MAX_PACKETS)
+       {
+               rasterizer.rasterize(packets, DE_NULL, MAX_PACKETS, numRasterized);
 
-       // 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));
+               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);
 
-       tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+                                       DE_ASSERT(deInBounds32(fragPos.x(), 0, maskMap.getWidth()));
+                                       DE_ASSERT(deInBounds32(fragPos.y(), 0, maskMap.getHeight()));
 
-       for (int lineNdx = 0; lineNdx < (int)scene.lines.size(); ++lineNdx)
+                                       const int                       previousMask    = maskMap.getPixelInt(fragPos.x(), fragPos.y()).x();
+                                       const int                       newMask                 = (previousMask) | (1UL << bitNdx);
+
+                                       maskMap.setPixel(tcu::IVec4(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)
        {
-               rr::SingleSampleLineRasterizer rasterizer(tcu::IVec4(0, 0, surface.getWidth(), surface.getHeight()));
+               const tcu::Vec2 pa = screenspaceLines[lineNdx].swizzle(0, 1);
+               const tcu::Vec2 pb = screenspaceLines[lineNdx].swizzle(2, 3);
 
-               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::Vec4 lineScreenSpace[2] =
-               {
-                       tcu::Vec4((lineNormalizedDeviceSpace[0].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[0].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
-                       tcu::Vec4((lineNormalizedDeviceSpace[1].x() + 1.0f) * 0.5f * (float)surface.getWidth(), (lineNormalizedDeviceSpace[1].y() + 1.0f) * 0.5f * (float)surface.getHeight(), 0.0f, 1.0f),
-               };
+               setMaskMapCoverageBitForLine(lineNdx, pa, pb, lineWidth, maskMap);
+       }
+}
 
-               rasterizer.init(lineScreenSpace[0], lineScreenSpace[1], scene.lineWidth);
+// 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);
 
-               // Calculate correct line coverage
-               for (;;)
-               {
-                       const int                       maxPackets                      = 32;
-                       int                                     numRasterized           = 0;
-                       rr::FragmentPacket      packets[maxPackets];
+       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)
 
-                       rasterizer.rasterize(packets, DE_NULL, maxPackets, numRasterized);
+       // 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());
 
-                       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);
-                                               const int                       previousMask    = referenceLineMap.getAccess().getPixelInt(fragPos.x(), fragPos.y()).x();
-                                               const int                       newMask                 = (previousMask) | (1UL << lineNdx);
+       tcu::clear(referenceLineMap.getAccess(), tcu::IVec4(0, 0, 0, 0));
+       tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
 
-                                               referenceLineMap.getAccess().setPixel(tcu::IVec4(newMask, 0, 0, 0), fragPos.x(), fragPos.y());
-                                       }
-                               }
-                       }
+       // log format
 
-                       if (numRasterized != maxPackets)
-                               break;
-               }
-       }
+       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
 
@@ -1446,7 +1527,7 @@ bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, cons
                bool                            matchFound                              = false;
                const tcu::IVec3        formatLimit                             ((1 << args.redBits) - 1, (1 << args.greenBits) - 1, (1 << args.blueBits) - 1);
 
-               std::vector<SingleSampleLineCoverageCandidate> candidates;
+               std::vector<SingleSampleNarrowLineCandidate> candidates;
 
                // Find lines with possible coverage
 
@@ -1469,27 +1550,30 @@ bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, cons
                {
                        if (((lineCoverageSet >> lineNdx) & 0x01) != 0)
                        {
-                               const LineInterpolationRange range = calcSingleSampleLineInterpolationRange(scene.lines[lineNdx].positions[0],
-                                                                                                                                                                                       scene.lines[lineNdx].positions[1],
-                                                                                                                                                                                       tcu::IVec2(x, y),
-                                                                                                                                                                                       viewportSize,
-                                                                                                                                                                                       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() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
-                                                                                                        de::clamp(valueMin.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
-                                                                                                        de::clamp(valueMin.z() * formatLimit.z(), 0.0f, (float)formatLimit.z()));
-                               const tcu::Vec3         colorMaxF               (de::clamp(valueMax.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
-                                                                                                        de::clamp(valueMax.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
-                                                                                                        de::clamp(valueMax.z() * 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()));
+                               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() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+                                                                                                                                de::clamp(valueMin.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+                                                                                                                                de::clamp(valueMin.z() * formatLimit.z(), 0.0f, (float)formatLimit.z()));
+                               const tcu::Vec3                                 colorMaxF               (de::clamp(valueMax.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+                                                                                                                                de::clamp(valueMax.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+                                                                                                                                de::clamp(valueMax.z() * 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() ||
@@ -1502,7 +1586,7 @@ bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, cons
                                        if (errorCount < errorFloodThreshold)
                                        {
                                                // Store candidate information for logging
-                                               SingleSampleLineCoverageCandidate candidate;
+                                               SingleSampleNarrowLineCandidate candidate;
 
                                                candidate.lineNdx               = lineNdx;
                                                candidate.colorMin              = colorMin;
@@ -1543,7 +1627,7 @@ bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, cons
 
                        for (int candidateNdx = 0; candidateNdx < (int)candidates.size(); ++candidateNdx)
                        {
-                               const SingleSampleLineCoverageCandidate& candidate = candidates[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"
@@ -1583,6 +1667,381 @@ bool verifySinglesampleLineGroupInterpolation (const tcu::Surface& surface, cons
        }
 }
 
+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() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+                                                                                                                                       de::clamp(valueMin.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+                                                                                                                                       de::clamp(valueMin.z() * formatLimit.z(), 0.0f, (float)formatLimit.z()));
+                                       const tcu::Vec3                                 colorMaxF               (de::clamp(valueMax.x() * formatLimit.x(), 0.0f, (float)formatLimit.x()),
+                                                                                                                                       de::clamp(valueMax.y() * formatLimit.y(), 0.0f, (float)formatLimit.y()),
+                                                                                                                                       de::clamp(valueMax.z() * 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)
@@ -1913,14 +2372,39 @@ bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const Triang
        return verifyTriangleGroupInterpolationWithInterpolator(surface, scene, args, log, TriangleInterpolator(scene));
 }
 
-bool verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
+LineInterpolationMethod verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log)
 {
        const bool multisampled = args.numSamples != 0;
 
        if (multisampled)
-               return verifyMultisampleLineGroupInterpolation(surface, scene, args, log);
+       {
+               if (verifyMultisampleLineGroupInterpolation(surface, scene, args, log))
+                       return LINEINTERPOLATION_STRICTLY_CORRECT;
+               return LINEINTERPOLATION_INCORRECT;
+       }
        else
-               return verifySinglesampleLineGroupInterpolation(surface, scene, args, log);
+       {
+               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
index 7b14ae9..af86283 100644 (file)
@@ -53,6 +53,13 @@ enum VerificationMode
        VERIFICATIONMODE_LAST
 };
 
+enum LineInterpolationMethod
+{
+       LINEINTERPOLATION_STRICTLY_CORRECT = 0, // !< line interpolation matches the specification
+       LINEINTERPOLATION_PROJECTED,                    // !< line interpolation weights are otherwise correct, but they are projected onto major axis
+       LINEINTERPOLATION_INCORRECT                             // !< line interpolation is incorrect
+};
+
 struct TriangleSceneSpec
 {
        struct SceneTriangle
@@ -158,9 +165,9 @@ bool verifyTriangleGroupInterpolation (const tcu::Surface& surface, const Triang
  *
  * The background is expected to be black.
  *
- * Returns false if invalid rasterization interpolation is found.
+ * Returns the detected interpolation method of the input image.
  *//*--------------------------------------------------------------------*/
-bool verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log);
+LineInterpolationMethod verifyLineGroupInterpolation (const tcu::Surface& surface, const LineSceneSpec& scene, const RasterizationArguments& args, tcu::TestLog& log);
 
 } // StateQueryUtil
 } // gls