/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Tessellation Tests.
 *//*--------------------------------------------------------------------*/

#include "es31fTessellationTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "glsShaderLibrary.hpp"
#include "glsStateQueryUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "gluPixelTransfer.hpp"
#include "gluDrawUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluVarType.hpp"
#include "gluVarTypeUtil.hpp"
#include "gluCallLogWrapper.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuSurface.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuImageIO.hpp"
#include "tcuResource.hpp"
#include "tcuImageCompare.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deSharedPtr.hpp"
#include "deUniquePtr.hpp"
#include "deString.h"
#include "deMath.h"

#include "glwEnums.hpp"
#include "glwDefs.hpp"
#include "glwFunctions.hpp"

#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include <set>
#include <limits>

using de::Random;
using de::SharedPtr;
using glu::RenderContext;
using glu::ShaderProgram;
using tcu::RenderTarget;
using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;

using std::string;
using std::vector;

using namespace glw; // For GL types.

namespace deqp
{

using gls::TextureTestUtil::RandomViewport;

namespace gles31
{
namespace Functional
{

using namespace gls::StateQueryUtil;

enum
{
    MINIMUM_MAX_TESS_GEN_LEVEL = 64 //!< GL-defined minimum for GL_MAX_TESS_GEN_LEVEL.
};

static inline bool vec3XLessThan(const Vec3 &a, const Vec3 &b)
{
    return a.x() < b.x();
}

template <typename IterT>
static string elemsStr(const IterT &begin, const IterT &end, int wrapLengthParam = 0, int numIndentationSpaces = 0)
{
    const string baseIndentation = string(numIndentationSpaces, ' ');
    const string deepIndentation = baseIndentation + string(4, ' ');
    const int wrapLength         = wrapLengthParam > 0 ? wrapLengthParam : std::numeric_limits<int>::max();
    const int length             = (int)std::distance(begin, end);
    string result;

    if (length > wrapLength)
        result += "(amount: " + de::toString(length) + ") ";
    result += string() + "{" + (length > wrapLength ? "\n" + deepIndentation : " ");

    {
        int index = 0;
        for (IterT it = begin; it != end; ++it)
        {
            if (it != begin)
                result += string() + ", " + (index % wrapLength == 0 ? "\n" + deepIndentation : "");
            result += de::toString(*it);
            index++;
        }

        result += length > wrapLength ? "\n" + baseIndentation : " ";
    }

    result += "}";
    return result;
}

template <typename ContainerT>
static string containerStr(const ContainerT &c, int wrapLengthParam = 0, int numIndentationSpaces = 0)
{
    return elemsStr(c.begin(), c.end(), wrapLengthParam, numIndentationSpaces);
}

template <typename T, int N>
static string arrayStr(const T (&arr)[N], int wrapLengthParam = 0, int numIndentationSpaces = 0)
{
    return elemsStr(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr), wrapLengthParam, numIndentationSpaces);
}

template <typename T, int N>
static T arrayMax(const T (&arr)[N])
{
    return *std::max_element(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
}

template <typename T, typename MembT>
static vector<MembT> members(const vector<T> &objs, MembT T::*membP)
{
    vector<MembT> result(objs.size());
    for (int i = 0; i < (int)objs.size(); i++)
        result[i] = objs[i].*membP;
    return result;
}

template <typename T, int N>
static vector<T> arrayToVector(const T (&arr)[N])
{
    return vector<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
}

template <typename ContainerT, typename T>
static inline bool contains(const ContainerT &c, const T &key)
{
    return c.find(key) != c.end();
}

template <int Size>
static inline tcu::Vector<bool, Size> singleTrueMask(int index)
{
    DE_ASSERT(de::inBounds(index, 0, Size));
    tcu::Vector<bool, Size> result;
    result[index] = true;
    return result;
}

static int intPow(int base, int exp)
{
    DE_ASSERT(exp >= 0);
    if (exp == 0)
        return 1;
    else
    {
        const int sub = intPow(base, exp / 2);
        if (exp % 2 == 0)
            return sub * sub;
        else
            return sub * sub * base;
    }
}

tcu::Surface getPixels(const glu::RenderContext &rCtx, int x, int y, int width, int height)
{
    tcu::Surface result(width, height);
    glu::readPixels(rCtx, x, y, result.getAccess());
    return result;
}

tcu::Surface getPixels(const glu::RenderContext &rCtx, const RandomViewport &vp)
{
    return getPixels(rCtx, vp.x, vp.y, vp.width, vp.height);
}

static inline void checkRenderTargetSize(const RenderTarget &renderTarget, int minSize)
{
    if (renderTarget.getWidth() < minSize || renderTarget.getHeight() < minSize)
        throw tcu::NotSupportedError("Render target width and height must be at least " + de::toString(minSize));
}

tcu::TextureLevel getPNG(const tcu::Archive &archive, const string &filename)
{
    tcu::TextureLevel result;
    tcu::ImageIO::loadPNG(result, archive, filename.c_str());
    return result;
}

static int numBasicSubobjects(const glu::VarType &type)
{
    if (type.isBasicType())
        return 1;
    else if (type.isArrayType())
        return type.getArraySize() * numBasicSubobjects(type.getElementType());
    else if (type.isStructType())
    {
        const glu::StructType &structType = *type.getStructPtr();
        int result                        = 0;
        for (int i = 0; i < structType.getNumMembers(); i++)
            result += numBasicSubobjects(structType.getMember(i).getType());
        return result;
    }
    else
    {
        DE_ASSERT(false);
        return -1;
    }
}

static inline int numVerticesPerPrimitive(uint32_t primitiveTypeGL)
{
    switch (primitiveTypeGL)
    {
    case GL_POINTS:
        return 1;
    case GL_TRIANGLES:
        return 3;
    case GL_LINES:
        return 2;
    default:
        DE_ASSERT(false);
        return -1;
    }
}

static inline void setViewport(const glw::Functions &gl, const RandomViewport &vp)
{
    gl.viewport(vp.x, vp.y, vp.width, vp.height);
}

static inline uint32_t getQueryResult(const glw::Functions &gl, uint32_t queryObject)
{
    uint32_t result = (uint32_t)-1;
    gl.getQueryObjectuiv(queryObject, GL_QUERY_RESULT, &result);
    TCU_CHECK(result != (uint32_t)-1);
    return result;
}

template <typename T>
static void readDataMapped(const glw::Functions &gl, uint32_t bufferTarget, int numElems, T *dst)
{
    const int numBytes        = numElems * (int)sizeof(T);
    const T *const mappedData = (const T *)gl.mapBufferRange(bufferTarget, 0, numBytes, GL_MAP_READ_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), (string() + "glMapBufferRange(" + glu::getBufferTargetName((int)bufferTarget) +
                                        ", 0, " + de::toString(numBytes) + ", GL_MAP_READ_BIT)")
                                           .c_str());
    TCU_CHECK(mappedData != DE_NULL);

    for (int i = 0; i < numElems; i++)
        dst[i] = mappedData[i];

    gl.unmapBuffer(bufferTarget);
}

template <typename T>
static vector<T> readDataMapped(const glw::Functions &gl, uint32_t bufferTarget, int numElems)
{
    vector<T> result(numElems);
    readDataMapped(gl, bufferTarget, numElems, &result[0]);
    return result;
}

namespace
{

template <typename ArgT, bool res>
struct ConstantUnaryPredicate
{
    bool operator()(const ArgT &) const
    {
        return res;
    }
};

//! Helper for handling simple, one-varying transform feedbacks.
template <typename VaryingT>
class TransformFeedbackHandler
{
public:
    struct Result
    {
        int numPrimitives;
        vector<VaryingT> varying;

        Result(void) : numPrimitives(-1)
        {
        }
        Result(int n, const vector<VaryingT> &v) : numPrimitives(n), varying(v)
        {
        }
    };

    TransformFeedbackHandler(const glu::RenderContext &renderCtx, int maxNumVertices);

    Result renderAndGetPrimitives(uint32_t programGL, uint32_t tfPrimTypeGL, int numBindings,
                                  const glu::VertexArrayBinding *bindings, int numVertices) const;

private:
    const glu::RenderContext &m_renderCtx;
    const glu::TransformFeedback m_tf;
    const glu::Buffer m_tfBuffer;
    const glu::Query m_tfPrimQuery;
};

template <typename AttribType>
TransformFeedbackHandler<AttribType>::TransformFeedbackHandler(const glu::RenderContext &renderCtx, int maxNumVertices)
    : m_renderCtx(renderCtx)
    , m_tf(renderCtx)
    , m_tfBuffer(renderCtx)
    , m_tfPrimQuery(renderCtx)
{
    const glw::Functions &gl = m_renderCtx.getFunctions();
    // \note Room for 1 extra triangle, to detect if GL returns too many primitives.
    const int bufferSize = (maxNumVertices + 3) * (int)sizeof(AttribType);

    gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer);
    gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_READ);
}

template <typename AttribType>
typename TransformFeedbackHandler<AttribType>::Result TransformFeedbackHandler<AttribType>::renderAndGetPrimitives(
    uint32_t programGL, uint32_t tfPrimTypeGL, int numBindings, const glu::VertexArrayBinding *bindings,
    int numVertices) const
{
    DE_ASSERT(tfPrimTypeGL == GL_POINTS || tfPrimTypeGL == GL_LINES || tfPrimTypeGL == GL_TRIANGLES);

    const glw::Functions &gl = m_renderCtx.getFunctions();

    gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, *m_tf);
    gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer);
    gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, *m_tfBuffer);

    gl.beginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, *m_tfPrimQuery);
    gl.beginTransformFeedback(tfPrimTypeGL);

    glu::draw(m_renderCtx, programGL, numBindings, bindings, glu::pr::Patches(numVertices));
    GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");

    gl.endTransformFeedback();
    gl.endQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

    {
        const int numPrimsWritten = (int)getQueryResult(gl, *m_tfPrimQuery);
        return Result(numPrimsWritten,
                      readDataMapped<AttribType>(gl, GL_TRANSFORM_FEEDBACK_BUFFER,
                                                 numPrimsWritten * numVerticesPerPrimitive(tfPrimTypeGL)));
    }
}

template <typename T>
class SizeLessThan
{
public:
    bool operator()(const T &a, const T &b) const
    {
        return a.size() < b.size();
    }
};

//! Predicate functor for comparing structs by their members.
template <typename Pred, typename T, typename MembT>
class MemberPred
{
public:
    MemberPred(MembT T::*membP) : m_membP(membP), m_pred(Pred())
    {
    }
    bool operator()(const T &a, const T &b) const
    {
        return m_pred(a.*m_membP, b.*m_membP);
    }

private:
    MembT T::*m_membP;
    Pred m_pred;
};

//! Convenience wrapper for MemberPred, because class template arguments aren't deduced based on constructor arguments.
template <template <typename> class Pred, typename T, typename MembT>
static MemberPred<Pred<MembT>, T, MembT> memberPred(MembT T::*membP)
{
    return MemberPred<Pred<MembT>, T, MembT>(membP);
}

template <typename SeqT, int Size, typename Pred>
class LexCompare
{
public:
    LexCompare(void) : m_pred(Pred())
    {
    }

    bool operator()(const SeqT &a, const SeqT &b) const
    {
        for (int i = 0; i < Size; i++)
        {
            if (m_pred(a[i], b[i]))
                return true;
            if (m_pred(b[i], a[i]))
                return false;
        }
        return false;
    }

private:
    Pred m_pred;
};

template <int Size>
class VecLexLessThan : public LexCompare<tcu::Vector<float, Size>, Size, std::less<float>>
{
};

enum TessPrimitiveType
{
    TESSPRIMITIVETYPE_TRIANGLES = 0,
    TESSPRIMITIVETYPE_QUADS,
    TESSPRIMITIVETYPE_ISOLINES,

    TESSPRIMITIVETYPE_LAST
};

enum SpacingMode
{
    SPACINGMODE_EQUAL,
    SPACINGMODE_FRACTIONAL_ODD,
    SPACINGMODE_FRACTIONAL_EVEN,

    SPACINGMODE_LAST
};

enum Winding
{
    WINDING_CCW = 0,
    WINDING_CW,

    WINDING_LAST
};

static inline const char *getTessPrimitiveTypeShaderName(TessPrimitiveType type)
{
    switch (type)
    {
    case TESSPRIMITIVETYPE_TRIANGLES:
        return "triangles";
    case TESSPRIMITIVETYPE_QUADS:
        return "quads";
    case TESSPRIMITIVETYPE_ISOLINES:
        return "isolines";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static inline const char *getSpacingModeShaderName(SpacingMode mode)
{
    switch (mode)
    {
    case SPACINGMODE_EQUAL:
        return "equal_spacing";
    case SPACINGMODE_FRACTIONAL_ODD:
        return "fractional_odd_spacing";
    case SPACINGMODE_FRACTIONAL_EVEN:
        return "fractional_even_spacing";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static inline const char *getWindingShaderName(Winding winding)
{
    switch (winding)
    {
    case WINDING_CCW:
        return "ccw";
    case WINDING_CW:
        return "cw";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static inline string getTessellationEvaluationInLayoutString(TessPrimitiveType primType, SpacingMode spacing,
                                                             Winding winding, bool usePointMode = false)
{
    return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) + ", " + getSpacingModeShaderName(spacing) +
           ", " + getWindingShaderName(winding) + (usePointMode ? ", point_mode" : "") + ") in;\n";
}

static inline string getTessellationEvaluationInLayoutString(TessPrimitiveType primType, SpacingMode spacing,
                                                             bool usePointMode = false)
{
    return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) + ", " + getSpacingModeShaderName(spacing) +
           (usePointMode ? ", point_mode" : "") + ") in;\n";
}

static inline string getTessellationEvaluationInLayoutString(TessPrimitiveType primType, Winding winding,
                                                             bool usePointMode = false)
{
    return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) + ", " + getWindingShaderName(winding) +
           (usePointMode ? ", point_mode" : "") + ") in;\n";
}

static inline string getTessellationEvaluationInLayoutString(TessPrimitiveType primType, bool usePointMode = false)
{
    return string() + "layout (" + getTessPrimitiveTypeShaderName(primType) + (usePointMode ? ", point_mode" : "") +
           ") in;\n";
}

static inline uint32_t outputPrimitiveTypeGL(TessPrimitiveType tessPrimType, bool usePointMode)
{
    if (usePointMode)
        return GL_POINTS;
    else
    {
        switch (tessPrimType)
        {
        case TESSPRIMITIVETYPE_TRIANGLES:
            return GL_TRIANGLES;
        case TESSPRIMITIVETYPE_QUADS:
            return GL_TRIANGLES;
        case TESSPRIMITIVETYPE_ISOLINES:
            return GL_LINES;
        default:
            DE_ASSERT(false);
            return (uint32_t)-1;
        }
    }
}

static inline int numInnerTessellationLevels(TessPrimitiveType primType)
{
    switch (primType)
    {
    case TESSPRIMITIVETYPE_TRIANGLES:
        return 1;
    case TESSPRIMITIVETYPE_QUADS:
        return 2;
    case TESSPRIMITIVETYPE_ISOLINES:
        return 0;
    default:
        DE_ASSERT(false);
        return -1;
    }
}

static inline int numOuterTessellationLevels(TessPrimitiveType primType)
{
    switch (primType)
    {
    case TESSPRIMITIVETYPE_TRIANGLES:
        return 3;
    case TESSPRIMITIVETYPE_QUADS:
        return 4;
    case TESSPRIMITIVETYPE_ISOLINES:
        return 2;
    default:
        DE_ASSERT(false);
        return -1;
    }
}

static string tessellationLevelsString(const float *inner, int numInner, const float *outer, int numOuter)
{
    DE_ASSERT(numInner >= 0 && numOuter >= 0);
    return "inner: " + elemsStr(inner, inner + numInner) + ", outer: " + elemsStr(outer, outer + numOuter);
}

static string tessellationLevelsString(const float *inner, const float *outer, TessPrimitiveType primType)
{
    return tessellationLevelsString(inner, numInnerTessellationLevels(primType), outer,
                                    numOuterTessellationLevels(primType));
}

static string tessellationLevelsString(const float *inner, const float *outer)
{
    return tessellationLevelsString(inner, 2, outer, 4);
}

static inline float getClampedTessLevel(SpacingMode mode, float tessLevel)
{
    switch (mode)
    {
    case SPACINGMODE_EQUAL:
        return de::max(1.0f, tessLevel);
    case SPACINGMODE_FRACTIONAL_ODD:
        return de::max(1.0f, tessLevel);
    case SPACINGMODE_FRACTIONAL_EVEN:
        return de::max(2.0f, tessLevel);
    default:
        DE_ASSERT(false);
        return -1.0f;
    }
}

static inline int getRoundedTessLevel(SpacingMode mode, float clampedTessLevel)
{
    int result = (int)deFloatCeil(clampedTessLevel);

    switch (mode)
    {
    case SPACINGMODE_EQUAL:
        break;
    case SPACINGMODE_FRACTIONAL_ODD:
        result += 1 - result % 2;
        break;
    case SPACINGMODE_FRACTIONAL_EVEN:
        result += result % 2;
        break;
    default:
        DE_ASSERT(false);
    }
    DE_ASSERT(de::inRange<int>(result, 1, MINIMUM_MAX_TESS_GEN_LEVEL));

    return result;
}

static int getClampedRoundedTessLevel(SpacingMode mode, float tessLevel)
{
    return getRoundedTessLevel(mode, getClampedTessLevel(mode, tessLevel));
}

//! A description of an outer edge of a triangle, quad or isolines.
//! An outer edge can be described by the index of a u/v/w coordinate
//! and the coordinate's value along that edge.
struct OuterEdgeDescription
{
    int constantCoordinateIndex;
    float constantCoordinateValueChoices[2];
    int numConstantCoordinateValueChoices;

    OuterEdgeDescription(int i, float c0) : constantCoordinateIndex(i), numConstantCoordinateValueChoices(1)
    {
        constantCoordinateValueChoices[0] = c0;
    }
    OuterEdgeDescription(int i, float c0, float c1) : constantCoordinateIndex(i), numConstantCoordinateValueChoices(2)
    {
        constantCoordinateValueChoices[0] = c0;
        constantCoordinateValueChoices[1] = c1;
    }

    string description(void) const
    {
        static const char *const coordinateNames[] = {"u", "v", "w"};
        string result;
        for (int i = 0; i < numConstantCoordinateValueChoices; i++)
            result += string() + (i > 0 ? " or " : "") + coordinateNames[constantCoordinateIndex] + "=" +
                      de::toString(constantCoordinateValueChoices[i]);
        return result;
    }

    bool contains(const Vec3 &v) const
    {
        for (int i = 0; i < numConstantCoordinateValueChoices; i++)
            if (v[constantCoordinateIndex] == constantCoordinateValueChoices[i])
                return true;
        return false;
    }
};

static vector<OuterEdgeDescription> outerEdgeDescriptions(TessPrimitiveType primType)
{
    static const OuterEdgeDescription triangleOuterEdgeDescriptions[3] = {
        OuterEdgeDescription(0, 0.0f), OuterEdgeDescription(1, 0.0f), OuterEdgeDescription(2, 0.0f)};

    static const OuterEdgeDescription quadOuterEdgeDescriptions[4] = {
        OuterEdgeDescription(0, 0.0f), OuterEdgeDescription(1, 0.0f), OuterEdgeDescription(0, 1.0f),
        OuterEdgeDescription(1, 1.0f)};

    static const OuterEdgeDescription isolinesOuterEdgeDescriptions[1] = {
        OuterEdgeDescription(0, 0.0f, 1.0f),
    };

    switch (primType)
    {
    case TESSPRIMITIVETYPE_TRIANGLES:
        return arrayToVector(triangleOuterEdgeDescriptions);
    case TESSPRIMITIVETYPE_QUADS:
        return arrayToVector(quadOuterEdgeDescriptions);
    case TESSPRIMITIVETYPE_ISOLINES:
        return arrayToVector(isolinesOuterEdgeDescriptions);
    default:
        DE_ASSERT(false);
        return vector<OuterEdgeDescription>();
    }
}

// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that u+v+w == 1.0f, or [uvw] + (1.0f-[uvw]) == 1.0f).
static vector<Vec3> generateReferenceTriangleTessCoords(SpacingMode spacingMode, int inner, int outer0, int outer1,
                                                        int outer2)
{
    vector<Vec3> tessCoords;

    if (inner == 1)
    {
        if (outer0 == 1 && outer1 == 1 && outer2 == 1)
        {
            tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f));
            tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f));
            tessCoords.push_back(Vec3(0.0f, 0.0f, 1.0f));
            return tessCoords;
        }
        else
            return generateReferenceTriangleTessCoords(spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
                                                       outer0, outer1, outer2);
    }
    else
    {
        for (int i = 0; i < outer0; i++)
        {
            const float v = (float)i / (float)outer0;
            tessCoords.push_back(Vec3(0.0f, v, 1.0f - v));
        }
        for (int i = 0; i < outer1; i++)
        {
            const float v = (float)i / (float)outer1;
            tessCoords.push_back(Vec3(1.0f - v, 0.0f, v));
        }
        for (int i = 0; i < outer2; i++)
        {
            const float v = (float)i / (float)outer2;
            tessCoords.push_back(Vec3(v, 1.0f - v, 0.0f));
        }

        const int numInnerTriangles = inner / 2;
        for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++)
        {
            const int curInnerTriangleLevel = inner - 2 * (innerTriangleNdx + 1);

            if (curInnerTriangleLevel == 0)
                tessCoords.push_back(Vec3(1.0f / 3.0f));
            else
            {
                const float minUVW    = (float)(2 * (innerTriangleNdx + 1)) / (float)(3 * inner);
                const float maxUVW    = 1.0f - 2.0f * minUVW;
                const Vec3 corners[3] = {Vec3(maxUVW, minUVW, minUVW), Vec3(minUVW, maxUVW, minUVW),
                                         Vec3(minUVW, minUVW, maxUVW)};

                for (int i = 0; i < curInnerTriangleLevel; i++)
                {
                    const float f = (float)i / (float)curInnerTriangleLevel;
                    for (int j = 0; j < 3; j++)
                        tessCoords.push_back((1.0f - f) * corners[j] + f * corners[(j + 1) % 3]);
                }
            }
        }

        return tessCoords;
    }
}

static int referenceTriangleNonPointModePrimitiveCount(SpacingMode spacingMode, int inner, int outer0, int outer1,
                                                       int outer2)
{
    if (inner == 1)
    {
        if (outer0 == 1 && outer1 == 1 && outer2 == 1)
            return 1;
        else
            return referenceTriangleNonPointModePrimitiveCount(
                spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2, outer0, outer1, outer2);
    }
    else
    {
        int result = outer0 + outer1 + outer2;

        const int numInnerTriangles = inner / 2;
        for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++)
        {
            const int curInnerTriangleLevel = inner - 2 * (innerTriangleNdx + 1);

            if (curInnerTriangleLevel == 1)
                result += 4;
            else
                result += 2 * 3 * curInnerTriangleLevel;
        }

        return result;
    }
}

// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f).
static vector<Vec3> generateReferenceQuadTessCoords(SpacingMode spacingMode, int inner0, int inner1, int outer0,
                                                    int outer1, int outer2, int outer3)
{
    vector<Vec3> tessCoords;

    if (inner0 == 1 || inner1 == 1)
    {
        if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1)
        {
            tessCoords.push_back(Vec3(0.0f, 0.0f, 0.0f));
            tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f));
            tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f));
            tessCoords.push_back(Vec3(1.0f, 1.0f, 0.0f));
            return tessCoords;
        }
        else
            return generateReferenceQuadTessCoords(spacingMode,
                                                   inner0 > 1                                ? inner0 :
                                                   spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 :
                                                                                               2,
                                                   inner1 > 1                                ? inner1 :
                                                   spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 :
                                                                                               2,
                                                   outer0, outer1, outer2, outer3);
    }
    else
    {
        for (int i = 0; i < outer0; i++)
        {
            const float v = (float)i / (float)outer0;
            tessCoords.push_back(Vec3(0.0f, v, 0.0f));
        }
        for (int i = 0; i < outer1; i++)
        {
            const float v = (float)i / (float)outer1;
            tessCoords.push_back(Vec3(1.0f - v, 0.0f, 0.0f));
        }
        for (int i = 0; i < outer2; i++)
        {
            const float v = (float)i / (float)outer2;
            tessCoords.push_back(Vec3(1.0f, 1.0f - v, 0.0f));
        }
        for (int i = 0; i < outer3; i++)
        {
            const float v = (float)i / (float)outer3;
            tessCoords.push_back(Vec3(v, 1.0f, 0.0f));
        }

        for (int innerVtxY = 0; innerVtxY < inner1 - 1; innerVtxY++)
            for (int innerVtxX = 0; innerVtxX < inner0 - 1; innerVtxX++)
                tessCoords.push_back(
                    Vec3((float)(innerVtxX + 1) / (float)inner0, (float)(innerVtxY + 1) / (float)inner1, 0.0f));

        return tessCoords;
    }
}

static int referenceQuadNonPointModePrimitiveCount(SpacingMode spacingMode, int inner0, int inner1, int outer0,
                                                   int outer1, int outer2, int outer3)
{
    vector<Vec3> tessCoords;

    if (inner0 == 1 || inner1 == 1)
    {
        if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1)
            return 2;
        else
            return referenceQuadNonPointModePrimitiveCount(spacingMode,
                                                           inner0 > 1                                ? inner0 :
                                                           spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 :
                                                                                                       2,
                                                           inner1 > 1                                ? inner1 :
                                                           spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 :
                                                                                                       2,
                                                           outer0, outer1, outer2, outer3);
    }
    else
        return 2 * (inner0 - 2) * (inner1 - 2) + 2 * (inner0 - 2) + 2 * (inner1 - 2) + outer0 + outer1 + outer2 +
               outer3;
}

// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f).
static vector<Vec3> generateReferenceIsolineTessCoords(int outer0, int outer1)
{
    vector<Vec3> tessCoords;

    for (int y = 0; y < outer0; y++)
        for (int x = 0; x < outer1 + 1; x++)
            tessCoords.push_back(Vec3((float)x / (float)outer1, (float)y / (float)outer0, 0.0f));

    return tessCoords;
}

static int referenceIsolineNonPointModePrimitiveCount(int outer0, int outer1)
{
    return outer0 * outer1;
}

static void getClampedRoundedTriangleTessLevels(SpacingMode spacingMode, const float *innerSrc, const float *outerSrc,
                                                int *innerDst, int *outerDst)
{
    innerDst[0] = getClampedRoundedTessLevel(spacingMode, innerSrc[0]);
    for (int i = 0; i < 3; i++)
        outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]);
}

static void getClampedRoundedQuadTessLevels(SpacingMode spacingMode, const float *innerSrc, const float *outerSrc,
                                            int *innerDst, int *outerDst)
{
    for (int i = 0; i < 2; i++)
        innerDst[i] = getClampedRoundedTessLevel(spacingMode, innerSrc[i]);
    for (int i = 0; i < 4; i++)
        outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]);
}

static void getClampedRoundedIsolineTessLevels(SpacingMode spacingMode, const float *outerSrc, int *outerDst)
{
    outerDst[0] = getClampedRoundedTessLevel(SPACINGMODE_EQUAL, outerSrc[0]);
    outerDst[1] = getClampedRoundedTessLevel(spacingMode, outerSrc[1]);
}

static inline bool isPatchDiscarded(TessPrimitiveType primitiveType, const float *outerLevels)
{
    const int numOuterLevels = numOuterTessellationLevels(primitiveType);
    for (int i = 0; i < numOuterLevels; i++)
        if (outerLevels[i] <= 0.0f)
            return true;
    return false;
}

static vector<Vec3> generateReferenceTessCoords(TessPrimitiveType primitiveType, SpacingMode spacingMode,
                                                const float *innerLevels, const float *outerLevels)
{
    if (isPatchDiscarded(primitiveType, outerLevels))
        return vector<Vec3>();

    switch (primitiveType)
    {
    case TESSPRIMITIVETYPE_TRIANGLES:
    {
        int inner;
        int outer[3];
        getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);

        if (spacingMode != SPACINGMODE_EQUAL)
        {
            // \note For fractional spacing modes, exact results are implementation-defined except in special cases.
            DE_ASSERT(de::abs(innerLevels[0] - (float)inner) < 0.001f);
            for (int i = 0; i < 3; i++)
                DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f);
            DE_ASSERT(inner > 1 || (outer[0] == 1 && outer[1] == 1 && outer[2] == 1));
        }

        return generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]);
    }

    case TESSPRIMITIVETYPE_QUADS:
    {
        int inner[2];
        int outer[4];
        getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);

        if (spacingMode != SPACINGMODE_EQUAL)
        {
            // \note For fractional spacing modes, exact results are implementation-defined except in special cases.
            for (int i = 0; i < 2; i++)
                DE_ASSERT(de::abs(innerLevels[i] - (float)inner[i]) < 0.001f);
            for (int i = 0; i < 4; i++)
                DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f);

            DE_ASSERT((inner[0] > 1 && inner[1] > 1) || (inner[0] == 1 && inner[1] == 1 && outer[0] == 1 &&
                                                         outer[1] == 1 && outer[2] == 1 && outer[3] == 1));
        }

        return generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]);
    }

    case TESSPRIMITIVETYPE_ISOLINES:
    {
        int outer[2];
        getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);

        if (spacingMode != SPACINGMODE_EQUAL)
        {
            // \note For fractional spacing modes, exact results are implementation-defined except in special cases.
            DE_ASSERT(de::abs(outerLevels[1] - (float)outer[1]) < 0.001f);
        }

        return generateReferenceIsolineTessCoords(outer[0], outer[1]);
    }

    default:
        DE_ASSERT(false);
        return vector<Vec3>();
    }
}

static int referencePointModePrimitiveCount(TessPrimitiveType primitiveType, SpacingMode spacingMode,
                                            const float *innerLevels, const float *outerLevels)
{
    if (isPatchDiscarded(primitiveType, outerLevels))
        return 0;

    switch (primitiveType)
    {
    case TESSPRIMITIVETYPE_TRIANGLES:
    {
        int inner;
        int outer[3];
        getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
        return (int)generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]).size();
    }

    case TESSPRIMITIVETYPE_QUADS:
    {
        int inner[2];
        int outer[4];
        getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
        return (int)generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2],
                                                    outer[3])
            .size();
    }

    case TESSPRIMITIVETYPE_ISOLINES:
    {
        int outer[2];
        getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
        return (int)generateReferenceIsolineTessCoords(outer[0], outer[1]).size();
    }

    default:
        DE_ASSERT(false);
        return -1;
    }
}

static int referenceNonPointModePrimitiveCount(TessPrimitiveType primitiveType, SpacingMode spacingMode,
                                               const float *innerLevels, const float *outerLevels)
{
    if (isPatchDiscarded(primitiveType, outerLevels))
        return 0;

    switch (primitiveType)
    {
    case TESSPRIMITIVETYPE_TRIANGLES:
    {
        int inner;
        int outer[3];
        getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
        return referenceTriangleNonPointModePrimitiveCount(spacingMode, inner, outer[0], outer[1], outer[2]);
    }

    case TESSPRIMITIVETYPE_QUADS:
    {
        int inner[2];
        int outer[4];
        getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
        return referenceQuadNonPointModePrimitiveCount(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2],
                                                       outer[3]);
    }

    case TESSPRIMITIVETYPE_ISOLINES:
    {
        int outer[2];
        getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
        return referenceIsolineNonPointModePrimitiveCount(outer[0], outer[1]);
    }

    default:
        DE_ASSERT(false);
        return -1;
    }
}

static int referencePrimitiveCount(TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode,
                                   const float *innerLevels, const float *outerLevels)
{
    return usePointMode ? referencePointModePrimitiveCount(primitiveType, spacingMode, innerLevels, outerLevels) :
                          referenceNonPointModePrimitiveCount(primitiveType, spacingMode, innerLevels, outerLevels);
}

static int referenceVertexCount(TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode,
                                const float *innerLevels, const float *outerLevels)
{
    return referencePrimitiveCount(primitiveType, spacingMode, usePointMode, innerLevels, outerLevels) *
           numVerticesPerPrimitive(outputPrimitiveTypeGL(primitiveType, usePointMode));
}

//! Helper for calling referenceVertexCount multiple times with different tessellation levels.
//! \note Levels contains inner and outer levels, per patch, in order IIOOOO. The full 6 levels must always be present, irrespective of primitiveType.
static int multiplePatchReferenceVertexCount(TessPrimitiveType primitiveType, SpacingMode spacingMode,
                                             bool usePointMode, const float *levels, int numPatches)
{
    int result = 0;
    for (int patchNdx = 0; patchNdx < numPatches; patchNdx++)
        result += referenceVertexCount(primitiveType, spacingMode, usePointMode, &levels[6 * patchNdx + 0],
                                       &levels[6 * patchNdx + 2]);
    return result;
}

vector<float> generateRandomPatchTessLevels(int numPatches, int constantOuterLevelIndex, float constantOuterLevel,
                                            de::Random &rnd)
{
    vector<float> tessLevels(numPatches * 6);

    for (int patchNdx = 0; patchNdx < numPatches; patchNdx++)
    {
        float *const inner = &tessLevels[patchNdx * 6 + 0];
        float *const outer = &tessLevels[patchNdx * 6 + 2];

        for (int j = 0; j < 2; j++)
            inner[j] = rnd.getFloat(1.0f, 62.0f);
        for (int j = 0; j < 4; j++)
            outer[j] = j == constantOuterLevelIndex ? constantOuterLevel : rnd.getFloat(1.0f, 62.0f);
    }

    return tessLevels;
}

static inline void drawPoint(tcu::Surface &dst, int centerX, int centerY, const tcu::RGBA &color, int size)
{
    const int width  = dst.getWidth();
    const int height = dst.getHeight();
    DE_ASSERT(de::inBounds(centerX, 0, width) && de::inBounds(centerY, 0, height));
    DE_ASSERT(size > 0);

    for (int yOff = -((size - 1) / 2); yOff <= size / 2; yOff++)
        for (int xOff = -((size - 1) / 2); xOff <= size / 2; xOff++)
        {
            const int pixX = centerX + xOff;
            const int pixY = centerY + yOff;
            if (de::inBounds(pixX, 0, width) && de::inBounds(pixY, 0, height))
                dst.setPixel(pixX, pixY, color);
        }
}

static void drawTessCoordPoint(tcu::Surface &dst, TessPrimitiveType primitiveType, const Vec3 &pt,
                               const tcu::RGBA &color, int size)
{
    // \note These coordinates should match the description in the log message in TessCoordCase::iterate.

    static const Vec2 triangleCorners[3] = {Vec2(0.95f, 0.95f), Vec2(0.5f, 0.95f - 0.9f * deFloatSqrt(3.0f / 4.0f)),
                                            Vec2(0.05f, 0.95f)};

    static const float quadIsolineLDRU[4] = {0.1f, 0.9f, 0.9f, 0.1f};

    const Vec2 dstPos = primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
                            pt.x() * triangleCorners[0] + pt.y() * triangleCorners[1] + pt.z() * triangleCorners[2]

                        :
                        primitiveType == TESSPRIMITIVETYPE_QUADS || primitiveType == TESSPRIMITIVETYPE_ISOLINES ?
                            Vec2((1.0f - pt.x()) * quadIsolineLDRU[0] + pt.x() * quadIsolineLDRU[2],
                                 (1.0f - pt.y()) * quadIsolineLDRU[1] + pt.y() * quadIsolineLDRU[3])

                            :
                            Vec2(-1.0f);

    drawPoint(dst, (int)(dstPos.x() * (float)dst.getWidth()), (int)(dstPos.y() * (float)dst.getHeight()), color, size);
}

static void drawTessCoordVisualization(tcu::Surface &dst, TessPrimitiveType primitiveType, const vector<Vec3> &coords)
{
    const int imageWidth  = 256;
    const int imageHeight = 256;
    dst.setSize(imageWidth, imageHeight);

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

    for (int i = 0; i < (int)coords.size(); i++)
        drawTessCoordPoint(dst, primitiveType, coords[i], tcu::RGBA::white(), 2);
}

static int binarySearchFirstVec3WithXAtLeast(const vector<Vec3> &sorted, float x)
{
    const Vec3 ref(x, 0.0f, 0.0f);
    const vector<Vec3>::const_iterator first = std::lower_bound(sorted.begin(), sorted.end(), ref, vec3XLessThan);
    if (first == sorted.end())
        return -1;
    return (int)std::distance(sorted.begin(), first);
}

template <typename T, typename P>
static vector<T> sorted(const vector<T> &unsorted, P pred)
{
    vector<T> result = unsorted;
    std::sort(result.begin(), result.end(), pred);
    return result;
}

template <typename T>
static vector<T> sorted(const vector<T> &unsorted)
{
    vector<T> result = unsorted;
    std::sort(result.begin(), result.end());
    return result;
}

// Check that all points in subset are (approximately) present also in superset.
static bool oneWayComparePointSets(TestLog &log, tcu::Surface &errorDst, TessPrimitiveType primitiveType,
                                   const vector<Vec3> &subset, const vector<Vec3> &superset, const char *subsetName,
                                   const char *supersetName, const tcu::RGBA &errorColor)
{
    const vector<Vec3> supersetSorted = sorted(superset, vec3XLessThan);
    const float epsilon               = 0.01f;
    const int maxNumFailurePrints     = 5;
    int numFailuresDetected           = 0;

    for (int subNdx = 0; subNdx < (int)subset.size(); subNdx++)
    {
        const Vec3 &subPt = subset[subNdx];

        bool matchFound = false;

        {
            // Binary search the index of the first point in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
            const Vec3 matchMin   = subPt - epsilon;
            const Vec3 matchMax   = subPt + epsilon;
            int firstCandidateNdx = binarySearchFirstVec3WithXAtLeast(supersetSorted, matchMin.x());

            if (firstCandidateNdx >= 0)
            {
                // Compare subPt to all points in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
                for (int superNdx = firstCandidateNdx;
                     superNdx < (int)supersetSorted.size() && supersetSorted[superNdx].x() <= matchMax.x(); superNdx++)
                {
                    const Vec3 &superPt = supersetSorted[superNdx];

                    if (tcu::boolAll(tcu::greaterThanEqual(superPt, matchMin)) &&
                        tcu::boolAll(tcu::lessThanEqual(superPt, matchMax)))
                    {
                        matchFound = true;
                        break;
                    }
                }
            }
        }

        if (!matchFound)
        {
            numFailuresDetected++;
            if (numFailuresDetected < maxNumFailurePrints)
                log << TestLog::Message << "Failure: no matching " << supersetName << " point found for " << subsetName
                    << " point " << subPt << TestLog::EndMessage;
            else if (numFailuresDetected == maxNumFailurePrints)
                log << TestLog::Message << "Note: More errors follow" << TestLog::EndMessage;

            drawTessCoordPoint(errorDst, primitiveType, subPt, errorColor, 4);
        }
    }

    return numFailuresDetected == 0;
}

static bool compareTessCoords(TestLog &log, TessPrimitiveType primitiveType, const vector<Vec3> &refCoords,
                              const vector<Vec3> &resCoords)
{
    tcu::Surface refVisual;
    tcu::Surface resVisual;
    bool success = true;

    drawTessCoordVisualization(refVisual, primitiveType, refCoords);
    drawTessCoordVisualization(resVisual, primitiveType, resCoords);

    // Check that all points in reference also exist in result.
    success = oneWayComparePointSets(log, refVisual, primitiveType, refCoords, resCoords, "reference", "result",
                                     tcu::RGBA::blue()) &&
              success;
    // Check that all points in result also exist in reference.
    success = oneWayComparePointSets(log, resVisual, primitiveType, resCoords, refCoords, "result", "reference",
                                     tcu::RGBA::red()) &&
              success;

    if (!success)
    {
        log << TestLog::Message
            << "Note: in the following reference visualization, points that are missing in result point set are blue "
               "(if any)"
            << TestLog::EndMessage
            << TestLog::Image("RefTessCoordVisualization", "Reference tessCoord visualization", refVisual)
            << TestLog::Message
            << "Note: in the following result visualization, points that are missing in reference point set are red "
               "(if any)"
            << TestLog::EndMessage;
    }

    log << TestLog::Image("ResTessCoordVisualization", "Result tessCoord visualization", resVisual);

    return success;
}

namespace VerifyFractionalSpacingSingleInternal
{

struct Segment
{
    int index; //!< Index of left coordinate in sortedXCoords.
    float length;
    Segment(void) : index(-1), length(-1.0f)
    {
    }
    Segment(int index_, float length_) : index(index_), length(length_)
    {
    }

    static vector<float> lengths(const vector<Segment> &segments)
    {
        return members(segments, &Segment::length);
    }
};

} // namespace VerifyFractionalSpacingSingleInternal

/*--------------------------------------------------------------------*//*!
 * \brief Verify fractional spacing conditions for a single line
 *
 * Verify that the splitting of an edge (resulting from e.g. an isoline
 * with outer levels { 1.0, tessLevel }) with a given fractional spacing
 * mode fulfills certain conditions given in the spec.
 *
 * Note that some conditions can't be checked from just one line
 * (specifically, that the additional segment decreases monotonically
 * length and the requirement that the additional segments be placed
 * identically for identical values of clamped level).
 *
 * Therefore, the function stores some values to additionalSegmentLengthDst
 * and additionalSegmentLocationDst that can later be given to
 * verifyFractionalSpacingMultiple(). A negative value in length means that
 * no additional segments are present, i.e. there's just one segment.
 * A negative value in location means that the value wasn't determinable,
 * i.e. all segments had same length.
 * The values are not stored if false is returned.
 *//*--------------------------------------------------------------------*/
static bool verifyFractionalSpacingSingle(TestLog &log, SpacingMode spacingMode, float tessLevel,
                                          const vector<float> &coords, float &additionalSegmentLengthDst,
                                          int &additionalSegmentLocationDst)
{
    using namespace VerifyFractionalSpacingSingleInternal;

    DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN);

    const float clampedLevel         = getClampedTessLevel(spacingMode, tessLevel);
    const int finalLevel             = getRoundedTessLevel(spacingMode, clampedLevel);
    const vector<float> sortedCoords = sorted(coords);
    string failNote                  = "Note: tessellation level is " + de::toString(tessLevel) +
                      "\nNote: sorted coordinates are:\n    " + containerStr(sortedCoords);

    if ((int)coords.size() != finalLevel + 1)
    {
        log << TestLog::Message << "Failure: number of vertices is " << coords.size() << "; expected " << finalLevel + 1
            << " (clamped tessellation level is " << clampedLevel << ")"
            << "; final level (clamped level rounded up to "
            << (spacingMode == SPACINGMODE_FRACTIONAL_EVEN ? "even" : "odd") << ") is " << finalLevel
            << " and should equal the number of segments, i.e. number of vertices minus 1" << TestLog::EndMessage
            << TestLog::Message << failNote << TestLog::EndMessage;
        return false;
    }

    if (sortedCoords[0] != 0.0f || sortedCoords.back() != 1.0f)
    {
        log << TestLog::Message << "Failure: smallest coordinate should be 0.0 and biggest should be 1.0"
            << TestLog::EndMessage << TestLog::Message << failNote << TestLog::EndMessage;
        return false;
    }

    {
        vector<Segment> segments(finalLevel);
        for (int i = 0; i < finalLevel; i++)
            segments[i] = Segment(i, sortedCoords[i + 1] - sortedCoords[i]);

        failNote += "\nNote: segment lengths are, from left to right:\n    " + containerStr(Segment::lengths(segments));

        {
            // Divide segments to two different groups based on length.

            vector<Segment> segmentsA;
            vector<Segment> segmentsB;
            segmentsA.push_back(segments[0]);

            for (int segNdx = 1; segNdx < (int)segments.size(); segNdx++)
            {
                const float epsilon = 0.001f;
                const Segment &seg  = segments[segNdx];

                if (de::abs(seg.length - segmentsA[0].length) < epsilon)
                    segmentsA.push_back(seg);
                else if (segmentsB.empty() || de::abs(seg.length - segmentsB[0].length) < epsilon)
                    segmentsB.push_back(seg);
                else
                {
                    log << TestLog::Message << "Failure: couldn't divide segments to 2 groups by length; "
                        << "e.g. segment of length " << seg.length << " isn't approximately equal to either "
                        << segmentsA[0].length << " or " << segmentsB[0].length << TestLog::EndMessage
                        << TestLog::Message << failNote << TestLog::EndMessage;
                    return false;
                }
            }

            if (clampedLevel == (float)finalLevel)
            {
                // All segments should be of equal length.
                if (!segmentsA.empty() && !segmentsB.empty())
                {
                    log << TestLog::Message
                        << "Failure: clamped and final tessellation level are equal, but not all segments are of equal "
                           "length."
                        << TestLog::EndMessage << TestLog::Message << failNote << TestLog::EndMessage;
                    return false;
                }
            }

            if (segmentsA.empty() || segmentsB.empty()) // All segments have same length. This is ok.
            {
                additionalSegmentLengthDst   = segments.size() == 1 ? -1.0f : segments[0].length;
                additionalSegmentLocationDst = -1;
                return true;
            }

            if (segmentsA.size() != 2 && segmentsB.size() != 2)
            {
                log << TestLog::Message
                    << "Failure: when dividing the segments to 2 groups by length, neither of the two groups has "
                       "exactly 2 or 0 segments in it"
                    << TestLog::EndMessage << TestLog::Message << failNote << TestLog::EndMessage;
                return false;
            }

            // For convenience, arrange so that the 2-segment group is segmentsB.
            if (segmentsB.size() != 2)
                std::swap(segmentsA, segmentsB);

            // \note For 4-segment lines both segmentsA and segmentsB have 2 segments each.
            //         Thus, we can't be sure which ones were meant as the additional segments.
            //         We give the benefit of the doubt by assuming that they're the shorter
            //         ones (as they should).

            if (segmentsA.size() != 2)
            {
                if (segmentsB[0].length > segmentsA[0].length + 0.001f)
                {
                    log << TestLog::Message << "Failure: the two additional segments are longer than the other segments"
                        << TestLog::EndMessage << TestLog::Message << failNote << TestLog::EndMessage;
                    return false;
                }
            }
            else
            {
                // We have 2 segmentsA and 2 segmentsB, ensure segmentsB has the shorter lengths
                if (segmentsB[0].length > segmentsA[0].length)
                    std::swap(segmentsA, segmentsB);
            }

            // Check that the additional segments are placed symmetrically.
            if (segmentsB[0].index + segmentsB[1].index + 1 != (int)segments.size())
            {
                log << TestLog::Message << "Failure: the two additional segments aren't placed symmetrically; "
                    << "one is at index " << segmentsB[0].index << " and other is at index " << segmentsB[1].index
                    << " (note: the two indexes should sum to " << (int)segments.size() - 1
                    << ", i.e. numberOfSegments-1)" << TestLog::EndMessage << TestLog::Message << failNote
                    << TestLog::EndMessage;
                return false;
            }

            additionalSegmentLengthDst = segmentsB[0].length;
            if (segmentsA.size() != 2)
                additionalSegmentLocationDst = de::min(segmentsB[0].index, segmentsB[1].index);
            else
                additionalSegmentLocationDst =
                    segmentsB[0].length < segmentsA[0].length - 0.001f ?
                        de::min(segmentsB[0].index, segmentsB[1].index) :
                        -1; // \note -1 when can't reliably decide which ones are the additional segments, a or b.

            return true;
        }
    }
}

namespace VerifyFractionalSpacingMultipleInternal
{

struct LineData
{
    float tessLevel;
    float additionalSegmentLength;
    int additionalSegmentLocation;
    LineData(float lev, float len, int loc)
        : tessLevel(lev)
        , additionalSegmentLength(len)
        , additionalSegmentLocation(loc)
    {
    }
};

} // namespace VerifyFractionalSpacingMultipleInternal

/*--------------------------------------------------------------------*//*!
 * \brief Verify fractional spacing conditions between multiple lines
 *
 * Verify the fractional spacing conditions that are not checked in
 * verifyFractionalSpacingSingle(). Uses values given by said function
 * as parameters, in addition to the spacing mode and tessellation level.
 *//*--------------------------------------------------------------------*/
static bool verifyFractionalSpacingMultiple(TestLog &log, SpacingMode spacingMode, const vector<float> &tessLevels,
                                            const vector<float> &additionalSegmentLengths,
                                            const vector<int> &additionalSegmentLocations)
{
    using namespace VerifyFractionalSpacingMultipleInternal;

    DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN);
    DE_ASSERT(tessLevels.size() == additionalSegmentLengths.size() &&
              tessLevels.size() == additionalSegmentLocations.size());

    vector<LineData> lineDatas;

    for (int i = 0; i < (int)tessLevels.size(); i++)
        lineDatas.push_back(LineData(tessLevels[i], additionalSegmentLengths[i], additionalSegmentLocations[i]));

    {
        const vector<LineData> lineDatasSortedByLevel = sorted(lineDatas, memberPred<std::less>(&LineData::tessLevel));

        // Check that lines with identical clamped tessellation levels have identical additionalSegmentLocation.

        for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++)
        {
            const LineData &curData  = lineDatasSortedByLevel[lineNdx];
            const LineData &prevData = lineDatasSortedByLevel[lineNdx - 1];

            if (curData.additionalSegmentLocation < 0 || prevData.additionalSegmentLocation < 0)
                continue; // Unknown locations, skip.

            if (getClampedTessLevel(spacingMode, curData.tessLevel) ==
                    getClampedTessLevel(spacingMode, prevData.tessLevel) &&
                curData.additionalSegmentLocation != prevData.additionalSegmentLocation)
            {
                log << TestLog::Message
                    << "Failure: additional segments not located identically for two edges with identical clamped "
                       "tessellation levels"
                    << TestLog::EndMessage << TestLog::Message << "Note: tessellation levels are " << curData.tessLevel
                    << " and " << prevData.tessLevel << " (clamped level "
                    << getClampedTessLevel(spacingMode, curData.tessLevel) << ")"
                    << "; but first additional segments located at indices " << curData.additionalSegmentLocation
                    << " and " << prevData.additionalSegmentLocation << ", respectively" << TestLog::EndMessage;
                return false;
            }
        }

        // Check that, among lines with same clamped rounded tessellation level, additionalSegmentLength is monotonically decreasing with "clampedRoundedTessLevel - clampedTessLevel" (the "fraction").

        for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++)
        {
            const LineData &curData  = lineDatasSortedByLevel[lineNdx];
            const LineData &prevData = lineDatasSortedByLevel[lineNdx - 1];

            if (curData.additionalSegmentLength < 0.0f || prevData.additionalSegmentLength < 0.0f)
                continue; // Unknown segment lengths, skip.

            const float curClampedLevel  = getClampedTessLevel(spacingMode, curData.tessLevel);
            const float prevClampedLevel = getClampedTessLevel(spacingMode, prevData.tessLevel);
            const int curFinalLevel      = getRoundedTessLevel(spacingMode, curClampedLevel);
            const int prevFinalLevel     = getRoundedTessLevel(spacingMode, prevClampedLevel);

            if (curFinalLevel != prevFinalLevel)
                continue;

            const float curFraction  = (float)curFinalLevel - curClampedLevel;
            const float prevFraction = (float)prevFinalLevel - prevClampedLevel;

            if (curData.additionalSegmentLength < prevData.additionalSegmentLength ||
                (curClampedLevel == prevClampedLevel &&
                 curData.additionalSegmentLength != prevData.additionalSegmentLength))
            {
                log << TestLog::Message
                    << "Failure: additional segment length isn't monotonically decreasing with the fraction <n> - <f>, "
                       "among edges with same final tessellation level"
                    << TestLog::EndMessage << TestLog::Message
                    << "Note: <f> stands for the clamped tessellation level and <n> for the final (rounded and "
                       "clamped) tessellation level"
                    << TestLog::EndMessage << TestLog::Message << "Note: two edges have tessellation levels "
                    << prevData.tessLevel << " and " << curData.tessLevel << " respectively"
                    << ", clamped " << prevClampedLevel << " and " << curClampedLevel << ", final " << prevFinalLevel
                    << " and " << curFinalLevel << "; fractions are " << prevFraction << " and " << curFraction
                    << ", but resulted in segment lengths " << prevData.additionalSegmentLength << " and "
                    << curData.additionalSegmentLength << TestLog::EndMessage;
                return false;
            }
        }
    }

    return true;
}

//! Compare triangle sets, ignoring triangle order and vertex order within triangle, and possibly exclude some triangles too.
template <typename IsTriangleRelevantT>
static bool compareTriangleSets(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, TestLog &log,
                                const IsTriangleRelevantT &isTriangleRelevant,
                                const char *ignoredTriangleDescription = DE_NULL)
{
    typedef tcu::Vector<Vec3, 3> Triangle;
    typedef LexCompare<Triangle, 3, VecLexLessThan<3>> TriangleLexLessThan;
    typedef std::set<Triangle, TriangleLexLessThan> TriangleSet;

    DE_ASSERT(coordsA.size() % 3 == 0 && coordsB.size() % 3 == 0);

    const int numTrianglesA = (int)coordsA.size() / 3;
    const int numTrianglesB = (int)coordsB.size() / 3;
    TriangleSet trianglesA;
    TriangleSet trianglesB;

    for (int aOrB = 0; aOrB < 2; aOrB++)
    {
        const vector<Vec3> &coords = aOrB == 0 ? coordsA : coordsB;
        const int numTriangles     = aOrB == 0 ? numTrianglesA : numTrianglesB;
        TriangleSet &triangles     = aOrB == 0 ? trianglesA : trianglesB;

        for (int triNdx = 0; triNdx < numTriangles; triNdx++)
        {
            Triangle triangle(coords[3 * triNdx + 0], coords[3 * triNdx + 1], coords[3 * triNdx + 2]);

            if (isTriangleRelevant(triangle.getPtr()))
            {
                std::sort(triangle.getPtr(), triangle.getPtr() + 3, VecLexLessThan<3>());
                triangles.insert(triangle);
            }
        }
    }

    {
        TriangleSet::const_iterator aIt = trianglesA.begin();
        TriangleSet::const_iterator bIt = trianglesB.begin();

        while (aIt != trianglesA.end() || bIt != trianglesB.end())
        {
            const bool aEnd = aIt == trianglesA.end();
            const bool bEnd = bIt == trianglesB.end();

            if (aEnd || bEnd || *aIt != *bIt)
            {
                log << TestLog::Message
                    << "Failure: triangle sets in two cases are not equal (when ignoring triangle and vertex order"
                    << (ignoredTriangleDescription == DE_NULL ? "" : string() + ", and " + ignoredTriangleDescription)
                    << ")" << TestLog::EndMessage;

                if (!aEnd && (bEnd || TriangleLexLessThan()(*aIt, *bIt)))
                    log << TestLog::Message << "Note: e.g. triangle " << *aIt
                        << " exists for first case but not for second" << TestLog::EndMessage;
                else
                    log << TestLog::Message << "Note: e.g. triangle " << *bIt
                        << " exists for second case but not for first" << TestLog::EndMessage;

                return false;
            }

            ++aIt;
            ++bIt;
        }

        return true;
    }
}

static bool compareTriangleSets(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, TestLog &log)
{
    return compareTriangleSets(coordsA, coordsB, log, ConstantUnaryPredicate<const Vec3 *, true>());
}

static bool supportsES32orGL45(Context &context)
{
    glu::ContextType contextType = context.getRenderContext().getType();
    return glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
           glu::contextSupports(contextType, glu::ApiType::core(4, 5));
}

static void checkGPUShader5Support(Context &context)
{
    TCU_CHECK_AND_THROW(NotSupportedError,
                        supportsES32orGL45(context) ||
                            context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"),
                        "GL_EXT_gpu_shader5 is not supported");
}

static void checkTessellationSupport(Context &context)
{
    TCU_CHECK_AND_THROW(NotSupportedError,
                        supportsES32orGL45(context) ||
                            context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"),
                        "GL_EXT_tessellation_shader is not supported");
}

static std::string specializeShader(Context &context, const char *code)
{
    const glu::ContextType contextType = context.getRenderContext().getType();
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(contextType);
    const bool cSupportsES32orGL45     = supportsES32orGL45(context);

    std::map<std::string, std::string> specializationMap = {
        {"GLSL_VERSION_DECL", glu::getGLSLVersionDeclaration(glslVersion)},
        {"GPU_SHADER5_REQUIRE", cSupportsES32orGL45 ? "" : "#extension GL_EXT_gpu_shader5 : require"},
        {"TESSELLATION_SHADER_REQUIRE", cSupportsES32orGL45 ? "" : "#extension GL_EXT_tessellation_shader : require"},
        {"GLSL_PER_VERTEX_OUT", ""}, // needed for GL4.5
        {"GLSL_PER_VERTEX_IN_ARR", ""},
        {"GLSL_PER_VERTEX_OUT_ARR", ""},
        {"GLSL_PRECISE_PER_VERTEX_OUT", ""},
        {"GLSL_PRECISE_PER_VERTEX_IN_ARR", ""},
        {"GLSL_PRECISE_PER_VERTEX_OUT_ARR", ""}};

    // for gl4.5 we need to add per vertex sections
    if (!glu::isContextTypeES(context.getRenderContext().getType()))
    {
        specializationMap["GLSL_PER_VERTEX_OUT"]     = "out gl_PerVertex { vec4 gl_Position; };\n";
        specializationMap["GLSL_PER_VERTEX_IN_ARR"]  = "in gl_PerVertex { vec4 gl_Position; } gl_in[];\n";
        specializationMap["GLSL_PER_VERTEX_OUT_ARR"] = "out gl_PerVertex { vec4 gl_Position; } gl_out[];\n";
        specializationMap["GLSL_PRECISE_PER_VERTEX_OUT"] =
            "out gl_PerVertex { vec4 gl_Position; };\nprecise gl_Position;\n";
        specializationMap["GLSL_PRECISE_PER_VERTEX_IN_ARR"] =
            "in gl_PerVertex { vec4 gl_Position; } gl_in[];\nprecise gl_in;\n";
        specializationMap["GLSL_PRECISE_PER_VERTEX_OUT_ARR"] =
            "out gl_PerVertex { vec4 gl_Position; } gl_out[];\nprecise gl_out;\n";
    }

    return tcu::StringTemplate(code).specialize(specializationMap);
}

// Draw primitives with shared edges and check that no cracks are visible at the shared edges.
class CommonEdgeCase : public TestCase
{
public:
    enum CaseType
    {
        CASETYPE_BASIC =
            0, //!< Order patch vertices such that when two patches share a vertex, it's at the same index for both.
        CASETYPE_PRECISE, //!< Vertex indices don't match like for CASETYPE_BASIC, but other measures are taken, using the 'precise' qualifier.

        CASETYPE_LAST
    };

    CommonEdgeCase(Context &context, const char *name, const char *description, TessPrimitiveType primitiveType,
                   SpacingMode spacing, CaseType caseType)
        : TestCase(context, name, description)
        , m_primitiveType(primitiveType)
        , m_spacing(spacing)
        , m_caseType(caseType)
    {
        DE_ASSERT(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES || m_primitiveType == TESSPRIMITIVETYPE_QUADS);
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static const int RENDER_SIZE = 256;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;
    const CaseType m_caseType;

    SharedPtr<const ShaderProgram> m_program;
};

void CommonEdgeCase::init(void)
{
    bool isGL45 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    if (!isGL45)
    {
        checkTessellationSupport(m_context);
        if (m_caseType == CASETYPE_PRECISE)
            checkGPUShader5Support(m_context);
    }

    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp vec2 in_v_position;\n"
                                     "in highp float in_v_tessParam;\n"
                                     "\n"
                                     "out highp vec2 in_tc_position;\n"
                                     "out highp float in_tc_tessParam;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_position = in_v_position;\n"
                                     "    in_tc_tessParam = in_v_tessParam;\n"
                                     "}\n");

    std::string tessellationControlTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n" +
        string(m_caseType == CASETYPE_PRECISE ? "${GPU_SHADER5_REQUIRE}\n" : "") +
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT_ARR}\n"
        "\n"
        "layout (vertices = " +
        string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" :
               m_primitiveType == TESSPRIMITIVETYPE_QUADS     ? "4" :
                                                                DE_NULL) +
        ") out;\n"
        "\n"
        "in highp vec2 in_tc_position[];\n"
        "in highp float in_tc_tessParam[];\n"
        "\n"
        "out highp vec2 in_te_position[];\n"
        "\n" +
        (m_caseType == CASETYPE_PRECISE ? "precise gl_TessLevelOuter;\n\n" : "") +
        "void main (void)\n"
        "{\n"
        "    in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n"
        "\n"
        "    gl_TessLevelInner[0] = 5.0;\n"
        "    gl_TessLevelInner[1] = 5.0;\n"
        "\n" +
        (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
             "    gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[2]);\n"
             "    gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[0]);\n"
             "    gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[1]);\n" :
         m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
             "    gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[2]);\n"
             "    gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[0]);\n"
             "    gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[3] + in_tc_tessParam[1]);\n"
             "    gl_TessLevelOuter[3] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[3]);\n" :
             deFatalStr("Invalid TessPrimitiveType")) +
        "}\n");

    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n" +
        string(m_caseType == CASETYPE_PRECISE ? "${GPU_SHADER5_REQUIRE}\n" : "") +
        "${GLSL_PRECISE_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PRECISE_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing) +
        "\n"
        "in highp vec2 in_te_position[];\n"
        "\n"
        "out mediump vec4 in_f_color;\n"
        "\n" +
        ((m_caseType == CASETYPE_PRECISE && !isGL45) ? "precise gl_Position;\n\n" : "") +
        "void main (void)\n"
        "{\n" +
        (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
             "    highp vec2 pos = gl_TessCoord.x*in_te_position[0] + gl_TessCoord.y*in_te_position[1] + "
             "gl_TessCoord.z*in_te_position[2];\n"
             "\n"
             "    highp float f = sqrt(3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z))) * 0.5 + 0.5;\n"
             "    in_f_color = vec4(gl_TessCoord*f, 1.0);\n" :
         m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
             string() +
                 (m_caseType == CASETYPE_BASIC ?
                      "    highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0]\n"
                      "                   + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1]\n"
                      "                   + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[2]\n"
                      "                   + (    gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[3];\n" :
                  m_caseType == CASETYPE_PRECISE ?
                      "    highp vec2 a = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0];\n"
                      "    highp vec2 b = (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1];\n"
                      "    highp vec2 c = (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[2];\n"
                      "    highp vec2 d = (    gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[3];\n"
                      "    highp vec2 pos = a+b+c+d;\n" :
                      deFatalStr("Invalid CaseType")) +
                 "\n"
                 "    highp float f = sqrt(1.0 - 2.0 * max(abs(gl_TessCoord.x - 0.5), abs(gl_TessCoord.y - 0.5)))*0.5 "
                 "+ "
                 "0.5;\n"
                 "    in_f_color = vec4(0.1, gl_TessCoord.xy*f, 1.0);\n" :
             deFatalStr("Invalid TessPrimitiveType")) +
        "\n"
        "    // Offset the position slightly, based on the parity of the bits in the float representation.\n"
        "    // This is done to detect possible small differences in edge vertex positions between patches.\n"
        "    uvec2 bits = floatBitsToUint(pos);\n"
        "    uint numBits = 0u;\n"
        "    for (uint i = 0u; i < 32u; i++)\n"
        "        numBits += ((bits[0] >> i) & 1u) + ((bits[1] >> i) & 1u);\n"
        "    pos += float(numBits&1u)*0.04;\n"
        "\n"
        "    gl_Position = vec4(pos, 0.0, 1.0);\n"
        "}\n");

    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in mediump vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void CommonEdgeCase::deinit(void)
{
    m_program.clear();
}

CommonEdgeCase::IterateResult CommonEdgeCase::iterate(void)
{
    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();

    const int gridWidth   = 4;
    const int gridHeight  = 4;
    const int numVertices = (gridWidth + 1) * (gridHeight + 1);
    const int numIndices  = gridWidth * gridHeight *
                           (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 * 2 :
                            m_primitiveType == TESSPRIMITIVETYPE_QUADS     ? 4 :
                                                                             -1);
    const int numPosCompsPerVertex = 2;
    const int totalNumPosComps     = numPosCompsPerVertex * numVertices;
    vector<float> gridPosComps;
    vector<float> gridTessParams;
    vector<uint16_t> gridIndices;

    gridPosComps.reserve(totalNumPosComps);
    gridTessParams.reserve(numVertices);
    gridIndices.reserve(numIndices);

    {
        for (int i = 0; i < gridHeight + 1; i++)
            for (int j = 0; j < gridWidth + 1; j++)
            {
                gridPosComps.push_back(-1.0f + 2.0f * ((float)j + 0.5f) / (float)(gridWidth + 1));
                gridPosComps.push_back(-1.0f + 2.0f * ((float)i + 0.5f) / (float)(gridHeight + 1));
                gridTessParams.push_back((float)(i * (gridWidth + 1) + j) / (float)(numVertices - 1));
            }
    }

    // Generate patch vertex indices.
    // \note If CASETYPE_BASIC, the vertices are ordered such that when multiple
    //         triangles/quads share a vertex, it's at the same index for everyone.

    if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
    {
        for (int i = 0; i < gridHeight; i++)
            for (int j = 0; j < gridWidth; j++)
            {
                const uint16_t corners[4] = {
                    (uint16_t)((i + 0) * (gridWidth + 1) + j + 0), (uint16_t)((i + 0) * (gridWidth + 1) + j + 1),
                    (uint16_t)((i + 1) * (gridWidth + 1) + j + 0), (uint16_t)((i + 1) * (gridWidth + 1) + j + 1)};

                const int secondTriangleVertexIndexOffset = m_caseType == CASETYPE_BASIC   ? 0 :
                                                            m_caseType == CASETYPE_PRECISE ? 1 :
                                                                                             -1;
                DE_ASSERT(secondTriangleVertexIndexOffset != -1);

                for (int k = 0; k < 3; k++)
                    gridIndices.push_back(corners[(k + 0 + i + (2 - j % 3)) % 3]);
                for (int k = 0; k < 3; k++)
                    gridIndices.push_back(corners[(k + 2 + i + (2 - j % 3) + secondTriangleVertexIndexOffset) % 3 + 1]);
            }
    }
    else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
    {
        for (int i = 0; i < gridHeight; i++)
            for (int j = 0; j < gridWidth; j++)
            {
                // \note The vertices are ordered such that when multiple quads
                //         share a vertices, it's at the same index for everyone.
                for (int m = 0; m < 2; m++)
                    for (int n = 0; n < 2; n++)
                        gridIndices.push_back((uint16_t)((i + (i + m) % 2) * (gridWidth + 1) + j + (j + n) % 2));

                if (m_caseType == CASETYPE_PRECISE && (i + j) % 2 == 0)
                    std::reverse(gridIndices.begin() + (gridIndices.size() - 4),
                                 gridIndices.begin() + gridIndices.size());
            }
    }
    else
        DE_ASSERT(false);

    DE_ASSERT((int)gridPosComps.size() == totalNumPosComps);
    DE_ASSERT((int)gridTessParams.size() == numVertices);
    DE_ASSERT((int)gridIndices.size() == numIndices);

    setViewport(gl, viewport);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.useProgram(programGL);

    {
        gl.patchParameteri(GL_PATCH_VERTICES, m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 :
                                              m_primitiveType == TESSPRIMITIVETYPE_QUADS     ? 4 :
                                                                                               -1);
        gl.clear(GL_COLOR_BUFFER_BIT);

        const glu::VertexArrayBinding attrBindings[] = {
            glu::va::Float("in_v_position", numPosCompsPerVertex, numVertices, 0, &gridPosComps[0]),
            glu::va::Float("in_v_tessParam", 1, numVertices, 0, &gridTessParams[0])};

        glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
                  glu::pr::Patches((int)gridIndices.size(), &gridIndices[0]));
        GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
    }

    {
        const tcu::Surface rendered = getPixels(renderCtx, viewport);

        log << TestLog::Image("RenderedImage", "Rendered Image", rendered) << TestLog::Message
            << "Note: coloring is done to clarify the positioning and orientation of the "
            << (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "triangles" :
                m_primitiveType == TESSPRIMITIVETYPE_QUADS     ? "quads" :
                                                                 DE_NULL)
            << "; the color of a vertex corresponds to the index of that vertex in the patch" << TestLog::EndMessage;

        if (m_caseType == CASETYPE_BASIC)
            log << TestLog::Message << "Note: each shared vertex has the same index among the primitives it belongs to"
                << TestLog::EndMessage;
        else if (m_caseType == CASETYPE_PRECISE)
            log << TestLog::Message << "Note: the 'precise' qualifier is used to avoid cracks between primitives"
                << TestLog::EndMessage;
        else
            DE_ASSERT(false);

        // Ad-hoc result verification - check that a certain rectangle in the image contains no black pixels.

        const int startX = (int)(0.15f * (float)rendered.getWidth());
        const int endX   = (int)(0.85f * (float)rendered.getWidth());
        const int startY = (int)(0.15f * (float)rendered.getHeight());
        const int endY   = (int)(0.85f * (float)rendered.getHeight());

        for (int y = startY; y < endY; y++)
            for (int x = startX; x < endX; x++)
            {
                const tcu::RGBA pixel = rendered.getPixel(x, y);

                if (pixel.getRed() == 0 && pixel.getGreen() == 0 && pixel.getBlue() == 0)
                {
                    log << TestLog::Message << "Failure: there seem to be cracks in the rendered result"
                        << TestLog::EndMessage << TestLog::Message
                        << "Note: pixel with zero r, g and b channels found at " << tcu::IVec2(x, y)
                        << TestLog::EndMessage;

                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
                    return STOP;
                }
            }

        log << TestLog::Message << "Success: there seem to be no cracks in the rendered result" << TestLog::EndMessage;
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

// Check tessellation coordinates (read with transform feedback).
class TessCoordCase : public TestCase
{
public:
    TessCoordCase(Context &context, const char *name, const char *description, TessPrimitiveType primitiveType,
                  SpacingMode spacing)
        : TestCase(context, name, description)
        , m_primitiveType(primitiveType)
        , m_spacing(spacing)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    struct TessLevels
    {
        float inner[2];
        float outer[4];
    };

    static const int RENDER_SIZE = 16;

    vector<TessLevels> genTessLevelCases(void) const;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;

    SharedPtr<const ShaderProgram> m_program;
};

void TessCoordCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "}\n");

    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = 1) out;\n"
                                            "\n"
                                            "uniform mediump float u_tessLevelInner0;\n"
                                            "uniform mediump float u_tessLevelInner1;\n"
                                            "\n"
                                            "uniform mediump float u_tessLevelOuter0;\n"
                                            "uniform mediump float u_tessLevelOuter1;\n"
                                            "uniform mediump float u_tessLevelOuter2;\n"
                                            "uniform mediump float u_tessLevelOuter3;\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_TessLevelInner[0] = u_tessLevelInner0;\n"
                                            "    gl_TessLevelInner[1] = u_tessLevelInner1;\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = u_tessLevelOuter0;\n"
                                            "    gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
                                            "    gl_TessLevelOuter[2] = u_tessLevelOuter2;\n"
                                            "    gl_TessLevelOuter[3] = u_tessLevelOuter3;\n"
                                            "}\n");

    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, true) +
        "\n"
        "out highp vec3 out_te_tessCoord;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    out_te_tessCoord = gl_TessCoord;\n"
        "    gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n"
        "}\n");

    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = vec4(1.0);\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                              << glu::TransformFeedbackVarying("out_te_tessCoord")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void TessCoordCase::deinit(void)
{
    m_program.clear();
}

vector<TessCoordCase::TessLevels> TessCoordCase::genTessLevelCases(void) const
{
    static const TessLevels rawTessLevelCases[] = {
        {{1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}}, {{63.0f, 24.0f}, {15.0f, 42.0f, 10.0f, 12.0f}},
        {{3.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{4.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
        {{2.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{5.0f, 6.0f}, {1.0f, 1.0f, 1.0f, 1.0f}},
        {{1.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}}, {{5.0f, 1.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
        {{5.2f, 1.6f}, {2.9f, 3.4f, 1.5f, 4.1f}}};

    if (m_spacing == SPACINGMODE_EQUAL)
        return vector<TessLevels>(DE_ARRAY_BEGIN(rawTessLevelCases), DE_ARRAY_END(rawTessLevelCases));
    else
    {
        vector<TessLevels> result;
        result.reserve(DE_LENGTH_OF_ARRAY(rawTessLevelCases));

        for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(rawTessLevelCases); tessLevelCaseNdx++)
        {
            TessLevels curTessLevelCase = rawTessLevelCases[tessLevelCaseNdx];

            float *const inner = &curTessLevelCase.inner[0];
            float *const outer = &curTessLevelCase.outer[0];

            for (int j = 0; j < 2; j++)
                inner[j] = (float)getClampedRoundedTessLevel(m_spacing, inner[j]);
            for (int j = 0; j < 4; j++)
                outer[j] = (float)getClampedRoundedTessLevel(m_spacing, outer[j]);

            if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
            {
                if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f)
                {
                    if (inner[0] == 1.0f)
                        inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f);
                }
            }
            else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
            {
                if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f || outer[3] > 1.0f)
                {
                    if (inner[0] == 1.0f)
                        inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f);
                    if (inner[1] == 1.0f)
                        inner[1] = (float)getClampedRoundedTessLevel(m_spacing, inner[1] + 0.1f);
                }
            }

            result.push_back(curTessLevelCase);
        }

        DE_ASSERT((int)result.size() == DE_LENGTH_OF_ARRAY(rawTessLevelCases));
        return result;
    }
}

TessCoordCase::IterateResult TessCoordCase::iterate(void)
{
    typedef TransformFeedbackHandler<Vec3> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();

    const int tessLevelInner0Loc = gl.getUniformLocation(programGL, "u_tessLevelInner0");
    const int tessLevelInner1Loc = gl.getUniformLocation(programGL, "u_tessLevelInner1");
    const int tessLevelOuter0Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter0");
    const int tessLevelOuter1Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter1");
    const int tessLevelOuter2Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter2");
    const int tessLevelOuter3Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter3");

    const vector<TessLevels> tessLevelCases = genTessLevelCases();
    vector<vector<Vec3>> caseReferences(tessLevelCases.size());

    for (int i = 0; i < (int)tessLevelCases.size(); i++)
        caseReferences[i] = generateReferenceTessCoords(m_primitiveType, m_spacing, &tessLevelCases[i].inner[0],
                                                        &tessLevelCases[i].outer[0]);

    const int maxNumVertices =
        (int)std::max_element(caseReferences.begin(), caseReferences.end(), SizeLessThan<vector<Vec3>>())->size();
    const TFHandler tfHandler(m_context.getRenderContext(), maxNumVertices);

    bool success = true;

    setViewport(gl, viewport);
    gl.useProgram(programGL);

    gl.patchParameteri(GL_PATCH_VERTICES, 1);

    for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
    {
        const float *const innerLevels = &tessLevelCases[tessLevelCaseNdx].inner[0];
        const float *const outerLevels = &tessLevelCases[tessLevelCaseNdx].outer[0];

        log << TestLog::Message
            << "Tessellation levels: " << tessellationLevelsString(innerLevels, outerLevels, m_primitiveType)
            << TestLog::EndMessage;

        gl.uniform1f(tessLevelInner0Loc, innerLevels[0]);
        gl.uniform1f(tessLevelInner1Loc, innerLevels[1]);
        gl.uniform1f(tessLevelOuter0Loc, outerLevels[0]);
        gl.uniform1f(tessLevelOuter1Loc, outerLevels[1]);
        gl.uniform1f(tessLevelOuter2Loc, outerLevels[2]);
        gl.uniform1f(tessLevelOuter3Loc, outerLevels[3]);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

        {
            const vector<Vec3> &tessCoordsRef = caseReferences[tessLevelCaseNdx];
            const TFHandler::Result tfResult  = tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1);

            if (tfResult.numPrimitives != (int)tessCoordsRef.size())
            {
                log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be "
                    << tfResult.numPrimitives << ", reference value is " << tessCoordsRef.size()
                    << " (logging further info anyway)" << TestLog::EndMessage;
                success = false;
            }
            else
                log << TestLog::Message << "Note: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be "
                    << tfResult.numPrimitives << TestLog::EndMessage;

            if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
                log << TestLog::Message
                    << "Note: in the following visualization(s), the u=1, v=1, w=1 corners are at the right, top, and "
                       "left corners, respectively"
                    << TestLog::EndMessage;
            else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES)
                log << TestLog::Message
                    << "Note: in the following visualization(s), u and v coordinate go left-to-right and "
                       "bottom-to-top, respectively"
                    << TestLog::EndMessage;
            else
                DE_ASSERT(false);

            success = compareTessCoords(log, m_primitiveType, tessCoordsRef, tfResult.varying) && success;
        }

        if (!success)
            break;
        else
            log << TestLog::Message << "All OK" << TestLog::EndMessage;
    }

    m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                            success ? "Pass" : "Invalid tessellation coordinates");
    return STOP;
}

// Check validity of fractional spacing modes. Draws a single isoline, reads tesscoords with transform feedback.
class FractionalSpacingModeCase : public TestCase
{
public:
    FractionalSpacingModeCase(Context &context, const char *name, const char *description, SpacingMode spacing)
        : TestCase(context, name, description)
        , m_spacing(spacing)
    {
        DE_ASSERT(m_spacing == SPACINGMODE_FRACTIONAL_EVEN || m_spacing == SPACINGMODE_FRACTIONAL_ODD);
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static const int RENDER_SIZE = 16;

    static vector<float> genTessLevelCases(void);

    const SpacingMode m_spacing;

    SharedPtr<const ShaderProgram> m_program;
};

void FractionalSpacingModeCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = 1) out;\n"
                                            "\n"
                                            "uniform mediump float u_tessLevelOuter1;\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_TessLevelOuter[0] = 1.0;\n"
                                            "    gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, m_spacing, true) +
        "\n"
        "out highp float out_te_tessCoord;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    out_te_tessCoord = gl_TessCoord.x;\n"
        "    gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = vec4(1.0);\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                              << glu::TransformFeedbackVarying("out_te_tessCoord")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void FractionalSpacingModeCase::deinit(void)
{
    m_program.clear();
}

vector<float> FractionalSpacingModeCase::genTessLevelCases(void)
{
    vector<float> result;

    // Ranges [7.0 .. 8.0), [8.0 .. 9.0) and [9.0 .. 10.0)
    {
        static const float rangeStarts[] = {7.0f, 8.0f, 9.0f};
        const int numSamplesPerRange     = 10;

        for (int rangeNdx = 0; rangeNdx < DE_LENGTH_OF_ARRAY(rangeStarts); rangeNdx++)
            for (int i = 0; i < numSamplesPerRange; i++)
                result.push_back(rangeStarts[rangeNdx] + (float)i / (float)numSamplesPerRange);
    }

    // 0.3, 1.3, 2.3,  ... , 62.3
    for (int i = 0; i <= 62; i++)
        result.push_back((float)i + 0.3f);

    return result;
}

FractionalSpacingModeCase::IterateResult FractionalSpacingModeCase::iterate(void)
{
    typedef TransformFeedbackHandler<float> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();

    const int tessLevelOuter1Loc = gl.getUniformLocation(programGL, "u_tessLevelOuter1");

    // Second outer tessellation levels.
    const vector<float> tessLevelCases = genTessLevelCases();
    const int maxNumVertices =
        1 + getClampedRoundedTessLevel(m_spacing, *std::max_element(tessLevelCases.begin(), tessLevelCases.end()));
    vector<float> additionalSegmentLengths;
    vector<int> additionalSegmentLocations;

    const TFHandler tfHandler(m_context.getRenderContext(), maxNumVertices);

    bool success = true;

    setViewport(gl, viewport);
    gl.useProgram(programGL);

    gl.patchParameteri(GL_PATCH_VERTICES, 1);

    for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
    {
        const float outerLevel1 = tessLevelCases[tessLevelCaseNdx];

        gl.uniform1f(tessLevelOuter1Loc, outerLevel1);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

        {
            const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1);
            float additionalSegmentLength;
            int additionalSegmentLocation;

            success = verifyFractionalSpacingSingle(log, m_spacing, outerLevel1, tfResult.varying,
                                                    additionalSegmentLength, additionalSegmentLocation);

            if (!success)
                break;

            additionalSegmentLengths.push_back(additionalSegmentLength);
            additionalSegmentLocations.push_back(additionalSegmentLocation);
        }
    }

    if (success)
        success = verifyFractionalSpacingMultiple(log, m_spacing, tessLevelCases, additionalSegmentLengths,
                                                  additionalSegmentLocations);

    m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                            success ? "Pass" : "Invalid tessellation coordinates");
    return STOP;
}

// Base class for a case with one input attribute (in_v_position) and optionally a TCS; tests with a couple of different sets of tessellation levels.
class BasicVariousTessLevelsPosAttrCase : public TestCase
{
public:
    BasicVariousTessLevelsPosAttrCase(Context &context, const char *name, const char *description,
                                      TessPrimitiveType primitiveType, SpacingMode spacing,
                                      const char *referenceImagePathPrefix)
        : TestCase(context, name, description)
        , m_primitiveType(primitiveType)
        , m_spacing(spacing)
        , m_referenceImagePathPrefix(referenceImagePathPrefix)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

protected:
    virtual const glu::ProgramSources makeSources(TessPrimitiveType, SpacingMode,
                                                  const char *vtxOutPosAttrName) const = DE_NULL;

private:
    static const int RENDER_SIZE = 256;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;
    const string m_referenceImagePathPrefix;

    SharedPtr<const ShaderProgram> m_program;
};

void BasicVariousTessLevelsPosAttrCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    {
        glu::ProgramSources sources = makeSources(m_primitiveType, m_spacing, "in_tc_position");
        DE_ASSERT(sources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].empty());

        std::string tessellationControlTemplate(
            "${GLSL_VERSION_DECL}\n"
            "${TESSELLATION_SHADER_REQUIRE}\n"
            "${GLSL_PER_VERTEX_IN_ARR}\n"
            "${GLSL_PER_VERTEX_OUT_ARR}\n"
            "\n"
            "layout (vertices = " +
            string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" : "4") +
            ") out;\n"
            "\n"
            "in highp vec2 in_tc_position[];\n"
            "\n"
            "out highp vec2 in_te_position[];\n"
            "\n"
            "uniform mediump float u_tessLevelInner0;\n"
            "uniform mediump float u_tessLevelInner1;\n"
            "uniform mediump float u_tessLevelOuter0;\n"
            "uniform mediump float u_tessLevelOuter1;\n"
            "uniform mediump float u_tessLevelOuter2;\n"
            "uniform mediump float u_tessLevelOuter3;\n"
            "\n"
            "void main (void)\n"
            "{\n"
            "    in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n"
            "\n"
            "    gl_TessLevelInner[0] = u_tessLevelInner0;\n"
            "    gl_TessLevelInner[1] = u_tessLevelInner1;\n"
            "\n"
            "    gl_TessLevelOuter[0] = u_tessLevelOuter0;\n"
            "    gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
            "    gl_TessLevelOuter[2] = u_tessLevelOuter2;\n"
            "    gl_TessLevelOuter[3] = u_tessLevelOuter3;\n"
            "}\n");

        sources << glu::TessellationControlSource(specializeShader(m_context, tessellationControlTemplate.c_str()));

        m_program = SharedPtr<const ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources));
    }

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void BasicVariousTessLevelsPosAttrCase::deinit(void)
{
    m_program.clear();
}

BasicVariousTessLevelsPosAttrCase::IterateResult BasicVariousTessLevelsPosAttrCase::iterate(void)
{
    static const struct
    {
        float inner[2];
        float outer[4];
    } tessLevelCases[] = {{{9.0f, 9.0f}, {9.0f, 9.0f, 9.0f, 9.0f}},
                          {{8.0f, 11.0f}, {13.0f, 15.0f, 18.0f, 21.0f}},
                          {{17.0f, 14.0f}, {3.0f, 6.0f, 9.0f, 12.0f}}};

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();
    const int patchSize      = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 :
                               m_primitiveType == TESSPRIMITIVETYPE_QUADS     ? 4 :
                               m_primitiveType == TESSPRIMITIVETYPE_ISOLINES  ? 4 :
                                                                                -1;

    setViewport(gl, viewport);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.useProgram(programGL);

    gl.patchParameteri(GL_PATCH_VERTICES, patchSize);

    for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(tessLevelCases); tessLevelCaseNdx++)
    {
        float innerLevels[2];
        float outerLevels[4];

        for (int i = 0; i < DE_LENGTH_OF_ARRAY(innerLevels); i++)
            innerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].inner[i]);

        for (int i = 0; i < DE_LENGTH_OF_ARRAY(outerLevels); i++)
            outerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].outer[i]);

        log << TestLog::Message
            << "Tessellation levels: " << tessellationLevelsString(&innerLevels[0], &outerLevels[0], m_primitiveType)
            << TestLog::EndMessage;

        gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner0"), innerLevels[0]);
        gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner1"), innerLevels[1]);
        gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter0"), outerLevels[0]);
        gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter1"), outerLevels[1]);
        gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter2"), outerLevels[2]);
        gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter3"), outerLevels[3]);

        gl.clear(GL_COLOR_BUFFER_BIT);

        {
            vector<Vec2> positions;
            positions.reserve(4);

            if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
            {
                positions.push_back(Vec2(0.8f, 0.6f));
                positions.push_back(Vec2(0.0f, -0.786f));
                positions.push_back(Vec2(-0.8f, 0.6f));
            }
            else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES)
            {
                positions.push_back(Vec2(-0.8f, -0.8f));
                positions.push_back(Vec2(0.8f, -0.8f));
                positions.push_back(Vec2(-0.8f, 0.8f));
                positions.push_back(Vec2(0.8f, 0.8f));
            }
            else
                DE_ASSERT(false);

            DE_ASSERT((int)positions.size() == patchSize);

            const glu::VertexArrayBinding attrBindings[] = {
                glu::va::Float("in_v_position", 2, (int)positions.size(), 0, &positions[0].x())};

            glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
                      glu::pr::Patches(patchSize));
            GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
        }

        {
            const tcu::Surface rendered       = getPixels(renderCtx, viewport);
            const tcu::TextureLevel reference = getPNG(
                m_testCtx.getArchive(), m_referenceImagePathPrefix + "_" + de::toString(tessLevelCaseNdx) + ".png");
            const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(),
                                                   rendered.getAccess(), 0.002f, tcu::COMPARE_LOG_RESULT);

            if (!success)
            {
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
                return STOP;
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

// Test that there are no obvious gaps in the triangulation of a tessellated triangle or quad.
class BasicTriangleFillCoverCase : public BasicVariousTessLevelsPosAttrCase
{
public:
    BasicTriangleFillCoverCase(Context &context, const char *name, const char *description,
                               TessPrimitiveType primitiveType, SpacingMode spacing,
                               const char *referenceImagePathPrefix)
        : BasicVariousTessLevelsPosAttrCase(context, name, description, primitiveType, spacing,
                                            referenceImagePathPrefix)
    {
        DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
    }

protected:
    void init(void)
    {
        checkGPUShader5Support(m_context);
        BasicVariousTessLevelsPosAttrCase::init();
    }

    const glu::ProgramSources makeSources(TessPrimitiveType primitiveType, SpacingMode spacing,
                                          const char *vtxOutPosAttrName) const
    {
        bool isGL45 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
        std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                         "${GLSL_PER_VERTEX_OUT}\n"
                                         "\n"
                                         "in highp vec2 in_v_position;\n"
                                         "\n"
                                         "out highp vec2 " +
                                         string(vtxOutPosAttrName) +
                                         ";\n"
                                         "\n"
                                         "void main (void)\n"
                                         "{\n"
                                         "    " +
                                         vtxOutPosAttrName +
                                         " = in_v_position;\n"
                                         "}\n");
        std::string tessellationEvaluationTemplate(
            "${GLSL_VERSION_DECL}\n"
            "${TESSELLATION_SHADER_REQUIRE}\n"
            "${GPU_SHADER5_REQUIRE}\n"
            "${GLSL_PER_VERTEX_IN_ARR}\n"
            "${GLSL_PRECISE_PER_VERTEX_OUT}\n"
            "\n" +
            getTessellationEvaluationInLayoutString(primitiveType, spacing) +
            "\n"
            "in highp vec2 in_te_position[];\n"
            "\n" +
            (isGL45 ? "" : "precise gl_Position;\n") +
            "void main (void)\n"
            "{\n" +
            (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
                 "\n"
                 "    highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n"
                 "    highp vec2 corner0 = in_te_position[0];\n"
                 "    highp vec2 corner1 = in_te_position[1];\n"
                 "    highp vec2 corner2 = in_te_position[2];\n"
                 "    highp vec2 pos =  corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n"
                 "    highp vec2 fromCenter = pos - (corner0 + corner1 + corner2) / 3.0;\n"
                 "    highp float f = (1.0 - length(fromCenter)) * (1.5 - d);\n"
                 "    pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n"
                 "    gl_Position = vec4(pos, 0.0, 1.0);\n" :
             primitiveType == TESSPRIMITIVETYPE_QUADS ?
                 "    highp vec2 corner0 = in_te_position[0];\n"
                 "    highp vec2 corner1 = in_te_position[1];\n"
                 "    highp vec2 corner2 = in_te_position[2];\n"
                 "    highp vec2 corner3 = in_te_position[3];\n"
                 "    highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
                 "                   + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
                 "                   + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
                 "                   + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
                 "    highp float d = 2.0 * min(abs(gl_TessCoord.x-0.5), abs(gl_TessCoord.y-0.5));\n"
                 "    highp vec2 fromCenter = pos - (corner0 + corner1 + corner2 + corner3) / 4.0;\n"
                 "    highp float f = (1.0 - length(fromCenter)) * sqrt(1.7 - d);\n"
                 "    pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n"
                 "    gl_Position = vec4(pos, 0.0, 1.0);\n" :
                 DE_NULL) +
            "}\n");
        std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                           "\n"
                                           "layout (location = 0) out mediump vec4 o_color;\n"
                                           "\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    o_color = vec4(1.0);\n"
                                           "}\n");

        return glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                                     << glu::TessellationEvaluationSource(
                                            specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                                     << glu::FragmentSource(
                                            specializeShader(m_context, fragmentShaderTemplate.c_str()));
    }
};

// Check that there are no obvious overlaps in the triangulation of a tessellated triangle or quad.
class BasicTriangleFillNonOverlapCase : public BasicVariousTessLevelsPosAttrCase
{
public:
    BasicTriangleFillNonOverlapCase(Context &context, const char *name, const char *description,
                                    TessPrimitiveType primitiveType, SpacingMode spacing,
                                    const char *referenceImagePathPrefix)
        : BasicVariousTessLevelsPosAttrCase(context, name, description, primitiveType, spacing,
                                            referenceImagePathPrefix)
    {
        DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
    }

protected:
    const glu::ProgramSources makeSources(TessPrimitiveType primitiveType, SpacingMode spacing,
                                          const char *vtxOutPosAttrName) const
    {
        std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                         "${GLSL_PER_VERTEX_OUT}\n"
                                         "\n"
                                         "in highp vec2 in_v_position;\n"
                                         "\n"
                                         "out highp vec2 " +
                                         string(vtxOutPosAttrName) +
                                         ";\n"
                                         "\n"
                                         "void main (void)\n"
                                         "{\n"
                                         "    " +
                                         vtxOutPosAttrName +
                                         " = in_v_position;\n"
                                         "}\n");
        std::string tessellationEvaluationTemplate(
            "${GLSL_VERSION_DECL}\n"
            "${TESSELLATION_SHADER_REQUIRE}\n"
            "${GLSL_PER_VERTEX_IN_ARR}\n"
            "${GLSL_PER_VERTEX_OUT}\n"
            "\n" +
            getTessellationEvaluationInLayoutString(primitiveType, spacing) +
            "\n"
            "in highp vec2 in_te_position[];\n"
            "\n"
            "out mediump vec4 in_f_color;\n"
            "\n"
            "uniform mediump float u_tessLevelInner0;\n"
            "uniform mediump float u_tessLevelInner1;\n"
            "\n"
            "void main (void)\n"
            "{\n" +
            (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
                 "\n"
                 "    highp vec2 corner0 = in_te_position[0];\n"
                 "    highp vec2 corner1 = in_te_position[1];\n"
                 "    highp vec2 corner2 = in_te_position[2];\n"
                 "    highp vec2 pos =  corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n"
                 "    gl_Position = vec4(pos, 0.0, 1.0);\n"
                 "    highp int numConcentricTriangles = int(round(u_tessLevelInner0)) / 2 + 1;\n"
                 "    highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n"
                 "    highp int phase = int(d*float(numConcentricTriangles)) % 3;\n"
                 "    in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
                 "               : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
                 "               :              vec4(0.0, 0.0, 1.0, 1.0);\n" :
             primitiveType == TESSPRIMITIVETYPE_QUADS ?
                 "    highp vec2 corner0 = in_te_position[0];\n"
                 "    highp vec2 corner1 = in_te_position[1];\n"
                 "    highp vec2 corner2 = in_te_position[2];\n"
                 "    highp vec2 corner3 = in_te_position[3];\n"
                 "    highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
                 "                   + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
                 "                   + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
                 "                   + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
                 "    gl_Position = vec4(pos, 0.0, 1.0);\n"
                 "    highp int phaseX = int(round((0.5 - abs(gl_TessCoord.x-0.5)) * u_tessLevelInner0));\n"
                 "    highp int phaseY = int(round((0.5 - abs(gl_TessCoord.y-0.5)) * u_tessLevelInner1));\n"
                 "    highp int phase = min(phaseX, phaseY) % 3;\n"
                 "    in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
                 "               : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
                 "               :              vec4(0.0, 0.0, 1.0, 1.0);\n" :
                 DE_NULL) +
            "}\n");
        std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                           "\n"
                                           "layout (location = 0) out mediump vec4 o_color;\n"
                                           "\n"
                                           "in mediump vec4 in_f_color;\n"
                                           "\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    o_color = in_f_color;\n"
                                           "}\n");

        return glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                                     << glu::TessellationEvaluationSource(
                                            specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                                     << glu::FragmentSource(
                                            specializeShader(m_context, fragmentShaderTemplate.c_str()));
    }
};

// Basic isolines rendering case.
class IsolinesRenderCase : public BasicVariousTessLevelsPosAttrCase
{
public:
    IsolinesRenderCase(Context &context, const char *name, const char *description, SpacingMode spacing,
                       const char *referenceImagePathPrefix)
        : BasicVariousTessLevelsPosAttrCase(context, name, description, TESSPRIMITIVETYPE_ISOLINES, spacing,
                                            referenceImagePathPrefix)
    {
    }

protected:
    const glu::ProgramSources makeSources(TessPrimitiveType primitiveType, SpacingMode spacing,
                                          const char *vtxOutPosAttrName) const
    {
        DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_ISOLINES);
        DE_UNREF(primitiveType);

        std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                         "${GLSL_PER_VERTEX_OUT}\n"
                                         "\n"
                                         "in highp vec2 in_v_position;\n"
                                         "\n"
                                         "out highp vec2 " +
                                         string(vtxOutPosAttrName) +
                                         ";\n"
                                         "\n"
                                         "void main (void)\n"
                                         "{\n"
                                         "    " +
                                         vtxOutPosAttrName +
                                         " = in_v_position;\n"
                                         "}\n");
        std::string tessellationEvaluationTemplate(
            "${GLSL_VERSION_DECL}\n"
            "${TESSELLATION_SHADER_REQUIRE}\n"
            "${GLSL_PER_VERTEX_IN_ARR}\n"
            "${GLSL_PER_VERTEX_OUT}\n"
            "\n" +
            getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, spacing) +
            "\n"
            "in highp vec2 in_te_position[];\n"
            "\n"
            "out mediump vec4 in_f_color;\n"
            "\n"
            "uniform mediump float u_tessLevelOuter0;\n"
            "uniform mediump float u_tessLevelOuter1;\n"
            "\n"
            "void main (void)\n"
            "{\n"
            "    highp vec2 corner0 = in_te_position[0];\n"
            "    highp vec2 corner1 = in_te_position[1];\n"
            "    highp vec2 corner2 = in_te_position[2];\n"
            "    highp vec2 corner3 = in_te_position[3];\n"
            "    highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
            "                   + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
            "                   + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
            "                   + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
            "    pos.y += 0.15*sin(gl_TessCoord.x*10.0);\n"
            "    gl_Position = vec4(pos, 0.0, 1.0);\n"
            "    highp int phaseX = int(round(gl_TessCoord.x*u_tessLevelOuter1));\n"
            "    highp int phaseY = int(round(gl_TessCoord.y*u_tessLevelOuter0));\n"
            "    highp int phase = (phaseX + phaseY) % 3;\n"
            "    in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
            "               : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
            "               :              vec4(0.0, 0.0, 1.0, 1.0);\n"
            "}\n");
        std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                           "\n"
                                           "layout (location = 0) out mediump vec4 o_color;\n"
                                           "\n"
                                           "in mediump vec4 in_f_color;\n"
                                           "\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    o_color = in_f_color;\n"
                                           "}\n");

        return glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                                     << glu::TessellationEvaluationSource(
                                            specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                                     << glu::FragmentSource(
                                            specializeShader(m_context, fragmentShaderTemplate.c_str()));
    }
};

// Test the "cw" and "ccw" TES input layout qualifiers.
class WindingCase : public TestCase
{
public:
    WindingCase(Context &context, const char *name, const char *description, TessPrimitiveType primitiveType,
                Winding winding)
        : TestCase(context, name, description)
        , m_primitiveType(primitiveType)
        , m_winding(winding)
    {
        DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static const int RENDER_SIZE = 64;

    const TessPrimitiveType m_primitiveType;
    const Winding m_winding;

    SharedPtr<const ShaderProgram> m_program;
};

void WindingCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = 1) out;\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_TessLevelInner[0] = 5.0;\n"
                                            "    gl_TessLevelInner[1] = 5.0;\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = 5.0;\n"
                                            "    gl_TessLevelOuter[1] = 5.0;\n"
                                            "    gl_TessLevelOuter[2] = 5.0;\n"
                                            "    gl_TessLevelOuter[3] = 5.0;\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate("${GLSL_VERSION_DECL}\n"
                                               "${TESSELLATION_SHADER_REQUIRE}\n"
                                               "${GLSL_PER_VERTEX_IN_ARR}\n"
                                               "${GLSL_PER_VERTEX_OUT}\n"
                                               "\n" +
                                               getTessellationEvaluationInLayoutString(m_primitiveType, m_winding) +
                                               "\n"
                                               "void main (void)\n"
                                               "{\n"
                                               "    gl_Position = vec4(gl_TessCoord.xy*2.0 - 1.0, 0.0, 1.0);\n"
                                               "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = vec4(1.0);\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void WindingCase::deinit(void)
{
    m_program.clear();
}

WindingCase::IterateResult WindingCase::iterate(void)
{
    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();
    const glu::VertexArray vao(renderCtx);

    bool success = true;

    setViewport(gl, viewport);
    gl.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
    gl.useProgram(programGL);

    gl.patchParameteri(GL_PATCH_VERTICES, 1);

    gl.enable(GL_CULL_FACE);

    gl.bindVertexArray(*vao);

    log << TestLog::Message << "Face culling enabled" << TestLog::EndMessage;

    for (int frontFaceWinding = 0; frontFaceWinding < WINDING_LAST; frontFaceWinding++)
    {
        log << TestLog::Message << "Setting glFrontFace(" << (frontFaceWinding == WINDING_CW ? "GL_CW" : "GL_CCW")
            << ")" << TestLog::EndMessage;

        gl.frontFace(frontFaceWinding == WINDING_CW ? GL_CW : GL_CCW);

        gl.clear(GL_COLOR_BUFFER_BIT);
        gl.drawArrays(GL_PATCHES, 0, 1);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");

        {
            const tcu::Surface rendered = getPixels(renderCtx, viewport);
            log << TestLog::Image("RenderedImage", "Rendered Image", rendered);

            {
                const int totalNumPixels    = rendered.getWidth() * rendered.getHeight();
                const int badPixelTolerance = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
                                                  5 * de::max(rendered.getWidth(), rendered.getHeight()) :
                                                  0;

                int numWhitePixels = 0;
                int numRedPixels   = 0;
                for (int y = 0; y < rendered.getHeight(); y++)
                    for (int x = 0; x < rendered.getWidth(); x++)
                    {
                        numWhitePixels += rendered.getPixel(x, y) == tcu::RGBA::white() ? 1 : 0;
                        numRedPixels += rendered.getPixel(x, y) == tcu::RGBA::red() ? 1 : 0;
                    }

                DE_ASSERT(numWhitePixels + numRedPixels <= totalNumPixels);

                log << TestLog::Message << "Note: got " << numWhitePixels << " white and " << numRedPixels
                    << " red pixels" << TestLog::EndMessage;

                if (totalNumPixels - numWhitePixels - numRedPixels > badPixelTolerance)
                {
                    log << TestLog::Message << "Failure: Got " << totalNumPixels - numWhitePixels - numRedPixels
                        << " other than white or red pixels (maximum tolerance " << badPixelTolerance << ")"
                        << TestLog::EndMessage;
                    success = false;
                    break;
                }

                if ((Winding)frontFaceWinding == m_winding)
                {
                    if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
                    {
                        if (de::abs(numWhitePixels - totalNumPixels / 2) > badPixelTolerance)
                        {
                            log << TestLog::Message << "Failure: wrong number of white pixels; expected approximately "
                                << totalNumPixels / 2 << TestLog::EndMessage;
                            success = false;
                            break;
                        }
                    }
                    else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
                    {
                        if (numWhitePixels != totalNumPixels)
                        {
                            log << TestLog::Message << "Failure: expected only white pixels (full-viewport quad)"
                                << TestLog::EndMessage;
                            success = false;
                            break;
                        }
                    }
                    else
                        DE_ASSERT(false);
                }
                else
                {
                    if (numWhitePixels != 0)
                    {
                        log << TestLog::Message << "Failure: expected only red pixels (everything culled)"
                            << TestLog::EndMessage;
                        success = false;
                        break;
                    }
                }
            }
        }
    }

    m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                            success ? "Pass" : "Image verification failed");
    return STOP;
}

// Test potentially differing input and output patch sizes.
class PatchVertexCountCase : public TestCase
{
public:
    PatchVertexCountCase(Context &context, const char *name, const char *description, int inputPatchSize,
                         int outputPatchSize, const char *referenceImagePath)
        : TestCase(context, name, description)
        , m_inputPatchSize(inputPatchSize)
        , m_outputPatchSize(outputPatchSize)
        , m_referenceImagePath(referenceImagePath)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static const int RENDER_SIZE = 256;

    const int m_inputPatchSize;
    const int m_outputPatchSize;

    const string m_referenceImagePath;

    SharedPtr<const ShaderProgram> m_program;
};

void PatchVertexCountCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    const string inSizeStr  = de::toString(m_inputPatchSize);
    const string outSizeStr = de::toString(m_outputPatchSize);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp float in_v_attr;\n"
                                     "\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = " +
                                            outSizeStr +
                                            ") out;\n"
                                            "\n"
                                            "in highp float in_tc_attr[];\n"
                                            "\n"
                                            "out highp float in_te_attr[];\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID*" +
                                            inSizeStr + "/" + outSizeStr +
                                            "];\n"
                                            "\n"
                                            "    gl_TessLevelInner[0] = 5.0;\n"
                                            "    gl_TessLevelInner[1] = 5.0;\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = 5.0;\n"
                                            "    gl_TessLevelOuter[1] = 5.0;\n"
                                            "    gl_TessLevelOuter[2] = 5.0;\n"
                                            "    gl_TessLevelOuter[3] = 5.0;\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
        "\n"
        "in highp float in_te_attr[];\n"
        "\n"
        "out mediump vec4 in_f_color;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    highp float x = gl_TessCoord.x*2.0 - 1.0;\n"
        "    highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" +
        outSizeStr +
        "-1)))];\n"
        "    gl_Position = vec4(x, y, 0.0, 1.0);\n"
        "    in_f_color = vec4(1.0);\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in mediump vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void PatchVertexCountCase::deinit(void)
{
    m_program.clear();
}

PatchVertexCountCase::IterateResult PatchVertexCountCase::iterate(void)
{
    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();

    setViewport(gl, viewport);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.useProgram(programGL);

    log << TestLog::Message << "Note: input patch size is " << m_inputPatchSize << ", output patch size is "
        << m_outputPatchSize << TestLog::EndMessage;

    {
        vector<float> attributeData;
        attributeData.reserve(m_inputPatchSize);

        for (int i = 0; i < m_inputPatchSize; i++)
        {
            const float f = (float)i / (float)(m_inputPatchSize - 1);
            attributeData.push_back(f * f);
        }

        gl.patchParameteri(GL_PATCH_VERTICES, m_inputPatchSize);
        gl.clear(GL_COLOR_BUFFER_BIT);

        const glu::VertexArrayBinding attrBindings[] = {
            glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])};

        glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
                  glu::pr::Patches(m_inputPatchSize));
        GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
    }

    {
        const tcu::Surface rendered       = getPixels(renderCtx, viewport);
        const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath);
        const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(),
                                               rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

        m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                success ? "Pass" : "Image comparison failed");
        return STOP;
    }
}

// Test per-patch inputs/outputs.
class PerPatchDataCase : public TestCase
{
public:
    enum CaseType
    {
        CASETYPE_PRIMITIVE_ID_TCS = 0,
        CASETYPE_PRIMITIVE_ID_TES,
        CASETYPE_PATCH_VERTICES_IN_TCS,
        CASETYPE_PATCH_VERTICES_IN_TES,
        CASETYPE_TESS_LEVEL_INNER0_TES,
        CASETYPE_TESS_LEVEL_INNER1_TES,
        CASETYPE_TESS_LEVEL_OUTER0_TES,
        CASETYPE_TESS_LEVEL_OUTER1_TES,
        CASETYPE_TESS_LEVEL_OUTER2_TES,
        CASETYPE_TESS_LEVEL_OUTER3_TES,

        CASETYPE_LAST
    };

    PerPatchDataCase(Context &context, const char *name, const char *description, CaseType caseType,
                     const char *referenceImagePath)
        : TestCase(context, name, description)
        , m_caseType(caseType)
        , m_referenceImagePath(caseTypeUsesRefImageFromFile(caseType) ? referenceImagePath : "")
    {
        DE_ASSERT(caseTypeUsesRefImageFromFile(caseType) == (referenceImagePath != DE_NULL));
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

    static const char *getCaseTypeName(CaseType);
    static const char *getCaseTypeDescription(CaseType);
    static bool caseTypeUsesRefImageFromFile(CaseType);

private:
    static const int RENDER_SIZE = 256;
    static const int INPUT_PATCH_SIZE;
    static const int OUTPUT_PATCH_SIZE;

    const CaseType m_caseType;
    const string m_referenceImagePath;

    SharedPtr<const ShaderProgram> m_program;
};

const int PerPatchDataCase::INPUT_PATCH_SIZE  = 10;
const int PerPatchDataCase::OUTPUT_PATCH_SIZE = 5;

const char *PerPatchDataCase::getCaseTypeName(CaseType type)
{
    switch (type)
    {
    case CASETYPE_PRIMITIVE_ID_TCS:
        return "primitive_id_tcs";
    case CASETYPE_PRIMITIVE_ID_TES:
        return "primitive_id_tes";
    case CASETYPE_PATCH_VERTICES_IN_TCS:
        return "patch_vertices_in_tcs";
    case CASETYPE_PATCH_VERTICES_IN_TES:
        return "patch_vertices_in_tes";
    case CASETYPE_TESS_LEVEL_INNER0_TES:
        return "tess_level_inner_0_tes";
    case CASETYPE_TESS_LEVEL_INNER1_TES:
        return "tess_level_inner_1_tes";
    case CASETYPE_TESS_LEVEL_OUTER0_TES:
        return "tess_level_outer_0_tes";
    case CASETYPE_TESS_LEVEL_OUTER1_TES:
        return "tess_level_outer_1_tes";
    case CASETYPE_TESS_LEVEL_OUTER2_TES:
        return "tess_level_outer_2_tes";
    case CASETYPE_TESS_LEVEL_OUTER3_TES:
        return "tess_level_outer_3_tes";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

const char *PerPatchDataCase::getCaseTypeDescription(CaseType type)
{
    switch (type)
    {
    case CASETYPE_PRIMITIVE_ID_TCS:
        return "Read gl_PrimitiveID in TCS and pass it as patch output to TES";
    case CASETYPE_PRIMITIVE_ID_TES:
        return "Read gl_PrimitiveID in TES";
    case CASETYPE_PATCH_VERTICES_IN_TCS:
        return "Read gl_PatchVerticesIn in TCS and pass it as patch output to TES";
    case CASETYPE_PATCH_VERTICES_IN_TES:
        return "Read gl_PatchVerticesIn in TES";
    case CASETYPE_TESS_LEVEL_INNER0_TES:
        return "Read gl_TessLevelInner[0] in TES";
    case CASETYPE_TESS_LEVEL_INNER1_TES:
        return "Read gl_TessLevelInner[1] in TES";
    case CASETYPE_TESS_LEVEL_OUTER0_TES:
        return "Read gl_TessLevelOuter[0] in TES";
    case CASETYPE_TESS_LEVEL_OUTER1_TES:
        return "Read gl_TessLevelOuter[1] in TES";
    case CASETYPE_TESS_LEVEL_OUTER2_TES:
        return "Read gl_TessLevelOuter[2] in TES";
    case CASETYPE_TESS_LEVEL_OUTER3_TES:
        return "Read gl_TessLevelOuter[3] in TES";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

bool PerPatchDataCase::caseTypeUsesRefImageFromFile(CaseType type)
{
    switch (type)
    {
    case CASETYPE_PRIMITIVE_ID_TCS:
    case CASETYPE_PRIMITIVE_ID_TES:
        return true;

    default:
        return false;
    }
}

void PerPatchDataCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    DE_ASSERT(OUTPUT_PATCH_SIZE < INPUT_PATCH_SIZE);

    const string inSizeStr  = de::toString(INPUT_PATCH_SIZE);
    const string outSizeStr = de::toString(OUTPUT_PATCH_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp float in_v_attr;\n"
                                     "\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT_ARR}\n"
        "\n"
        "layout (vertices = " +
        outSizeStr +
        ") out;\n"
        "\n"
        "in highp float in_tc_attr[];\n"
        "\n"
        "out highp float in_te_attr[];\n" +
        (m_caseType == CASETYPE_PRIMITIVE_ID_TCS      ? "patch out mediump int in_te_primitiveIDFromTCS;\n" :
         m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ? "patch out mediump int in_te_patchVerticesInFromTCS;\n" :
                                                        "") +
        "\n"
        "void main (void)\n"
        "{\n"
        "    in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n" +
        (m_caseType == CASETYPE_PRIMITIVE_ID_TCS      ? "\tin_te_primitiveIDFromTCS = gl_PrimitiveID;\n" :
         m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ? "\tin_te_patchVerticesInFromTCS = gl_PatchVerticesIn;\n" :
                                                        "") +
        "\n"
        "    gl_TessLevelInner[0] = 9.0;\n"
        "    gl_TessLevelInner[1] = 8.0;\n"
        "\n"
        "    gl_TessLevelOuter[0] = 7.0;\n"
        "    gl_TessLevelOuter[1] = 6.0;\n"
        "    gl_TessLevelOuter[2] = 5.0;\n"
        "    gl_TessLevelOuter[3] = 4.0;\n"
        "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
        "\n"
        "in highp float in_te_attr[];\n" +
        (m_caseType == CASETYPE_PRIMITIVE_ID_TCS      ? "patch in mediump int in_te_primitiveIDFromTCS;\n" :
         m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ? "patch in mediump int in_te_patchVerticesInFromTCS;\n" :
                                                        string()) +
        "\n"
        "out mediump vec4 in_f_color;\n"
        "\n"
        "uniform highp float u_xScale;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    highp float x = (gl_TessCoord.x*u_xScale + in_te_attr[0]) * 2.0 - 1.0;\n"
        "    highp float y = gl_TessCoord.y*2.0 - 1.0;\n"
        "    gl_Position = vec4(x, y, 0.0, 1.0);\n" +
        (m_caseType == CASETYPE_PRIMITIVE_ID_TCS ? "\tbool ok = in_te_primitiveIDFromTCS == 3;\n" :
         m_caseType == CASETYPE_PRIMITIVE_ID_TES ? "\tbool ok = gl_PrimitiveID == 3;\n" :
         m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS ?
                                                   "\tbool ok = in_te_patchVerticesInFromTCS == " + inSizeStr + ";\n" :
         m_caseType == CASETYPE_PATCH_VERTICES_IN_TES ? "\tbool ok = gl_PatchVerticesIn == " + outSizeStr + ";\n" :
         m_caseType == CASETYPE_TESS_LEVEL_INNER0_TES ? "\tbool ok = abs(gl_TessLevelInner[0] - 9.0) < 0.1f;\n" :
         m_caseType == CASETYPE_TESS_LEVEL_INNER1_TES ? "\tbool ok = abs(gl_TessLevelInner[1] - 8.0) < 0.1f;\n" :
         m_caseType == CASETYPE_TESS_LEVEL_OUTER0_TES ? "\tbool ok = abs(gl_TessLevelOuter[0] - 7.0) < 0.1f;\n" :
         m_caseType == CASETYPE_TESS_LEVEL_OUTER1_TES ? "\tbool ok = abs(gl_TessLevelOuter[1] - 6.0) < 0.1f;\n" :
         m_caseType == CASETYPE_TESS_LEVEL_OUTER2_TES ? "\tbool ok = abs(gl_TessLevelOuter[2] - 5.0) < 0.1f;\n" :
         m_caseType == CASETYPE_TESS_LEVEL_OUTER3_TES ? "\tbool ok = abs(gl_TessLevelOuter[3] - 4.0) < 0.1f;\n" :
                                                        DE_NULL) +
        "    in_f_color = ok ? vec4(1.0) : vec4(vec3(0.0), 1.0);\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in mediump vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void PerPatchDataCase::deinit(void)
{
    m_program.clear();
}

PerPatchDataCase::IterateResult PerPatchDataCase::iterate(void)
{
    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();

    setViewport(gl, viewport);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.useProgram(programGL);

    log << TestLog::Message << "Note: input patch size is " << INPUT_PATCH_SIZE << ", output patch size is "
        << OUTPUT_PATCH_SIZE << TestLog::EndMessage;

    {
        const int numPrimitives =
            m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES ? 8 : 1;

        vector<float> attributeData;
        attributeData.reserve(numPrimitives * INPUT_PATCH_SIZE);

        for (int i = 0; i < numPrimitives; i++)
        {
            attributeData.push_back((float)i / (float)numPrimitives);
            for (int j = 0; j < INPUT_PATCH_SIZE - 1; j++)
                attributeData.push_back(0.0f);
        }

        gl.patchParameteri(GL_PATCH_VERTICES, INPUT_PATCH_SIZE);
        gl.clear(GL_COLOR_BUFFER_BIT);

        gl.uniform1f(gl.getUniformLocation(programGL, "u_xScale"), 1.0f / (float)numPrimitives);

        const glu::VertexArrayBinding attrBindings[] = {
            glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])};

        glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
                  glu::pr::Patches(numPrimitives * INPUT_PATCH_SIZE));
        GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
    }

    {
        const tcu::Surface rendered = getPixels(renderCtx, viewport);

        if (m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES)
        {
            DE_ASSERT(caseTypeUsesRefImageFromFile(m_caseType));

            const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath);
            const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(),
                                                   rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

            m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    success ? "Pass" : "Image comparison failed");
            return STOP;
        }
        else
        {
            DE_ASSERT(!caseTypeUsesRefImageFromFile(m_caseType));

            log << TestLog::Image("RenderedImage", "Rendered Image", rendered);

            for (int y = 0; y < rendered.getHeight(); y++)
                for (int x = 0; x < rendered.getWidth(); x++)
                {
                    if (rendered.getPixel(x, y) != tcu::RGBA::white())
                    {
                        log << TestLog::Message << "Failure: expected all white pixels" << TestLog::EndMessage;
                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
                        return STOP;
                    }
                }

            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
            return STOP;
        }
    }
}

// Basic barrier() usage in TCS.
class BarrierCase : public TestCase
{
public:
    BarrierCase(Context &context, const char *name, const char *description, const char *referenceImagePath)
        : TestCase(context, name, description)
        , m_referenceImagePath(referenceImagePath)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static const int RENDER_SIZE = 256;
    static const int NUM_VERTICES;

    const string m_referenceImagePath;

    SharedPtr<const ShaderProgram> m_program;
};

const int BarrierCase::NUM_VERTICES = 32;

void BarrierCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    const string numVertsStr = de::toString(NUM_VERTICES);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp float in_v_attr;\n"
                                     "\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = " +
                                            numVertsStr +
                                            ") out;\n"
                                            "\n"
                                            "in highp float in_tc_attr[];\n"
                                            "\n"
                                            "out highp float in_te_attr[];\n"
                                            "patch out highp float in_te_patchAttr;\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n"
                                            "    in_te_patchAttr = 0.0f;\n"
                                            "    barrier();\n"
                                            "    if (gl_InvocationID == 5)\n"
                                            "        in_te_patchAttr = float(gl_InvocationID)*0.1;\n"
                                            "    barrier();\n"
                                            "    highp float temp = in_te_patchAttr + in_te_attr[gl_InvocationID];\n"
                                            "    barrier();\n"
                                            "    if (gl_InvocationID == " +
                                            numVertsStr +
                                            "-1)\n"
                                            "        in_te_patchAttr = float(gl_InvocationID);\n"
                                            "    barrier();\n"
                                            "    in_te_attr[gl_InvocationID] = temp;\n"
                                            "    barrier();\n"
                                            "    temp = temp + in_te_attr[(gl_InvocationID+1) % " +
                                            numVertsStr +
                                            "];\n"
                                            "    barrier();\n"
                                            "    in_te_attr[gl_InvocationID] = 0.25*temp;\n"
                                            "\n"
                                            "    gl_TessLevelInner[0] = 32.0;\n"
                                            "    gl_TessLevelInner[1] = 32.0;\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = 32.0;\n"
                                            "    gl_TessLevelOuter[1] = 32.0;\n"
                                            "    gl_TessLevelOuter[2] = 32.0;\n"
                                            "    gl_TessLevelOuter[3] = 32.0;\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
        "\n"
        "in highp float in_te_attr[];\n"
        "patch in highp float in_te_patchAttr;\n"
        "\n"
        "out highp float in_f_blue;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    highp float x = gl_TessCoord.x*2.0 - 1.0;\n"
        "    highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" +
        numVertsStr +
        "-1)))];\n"
        "    gl_Position = vec4(x, y, 0.0, 1.0);\n"
        "    in_f_blue = abs(in_te_patchAttr - float(" +
        numVertsStr +
        "-1));\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in highp float in_f_blue;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = vec4(1.0, 0.0, in_f_blue, 1.0);\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void BarrierCase::deinit(void)
{
    m_program.clear();
}

BarrierCase::IterateResult BarrierCase::iterate(void)
{
    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const uint32_t programGL = m_program->getProgram();
    const glw::Functions &gl = renderCtx.getFunctions();

    setViewport(gl, viewport);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.useProgram(programGL);

    {
        vector<float> attributeData(NUM_VERTICES);

        for (int i = 0; i < NUM_VERTICES; i++)
            attributeData[i] = (float)i / (float)(NUM_VERTICES - 1);

        gl.patchParameteri(GL_PATCH_VERTICES, NUM_VERTICES);
        gl.clear(GL_COLOR_BUFFER_BIT);

        const glu::VertexArrayBinding attrBindings[] = {
            glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])};

        glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
                  glu::pr::Patches(NUM_VERTICES));
        GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
    }

    {
        const tcu::Surface rendered       = getPixels(renderCtx, viewport);
        const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath);
        const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(),
                                               rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

        m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                success ? "Pass" : "Image comparison failed");
        return STOP;
    }
}

/*--------------------------------------------------------------------*//*!
 * \brief Base class for testing invariance of entire primitive set
 *
 * Draws two patches with identical tessellation levels and compares the
 * results. Repeats the same with other programs that are only different
 * in irrelevant ways; compares the results between these two programs.
 * Also potentially compares to results produced by different tessellation
 * levels (see e.g. invariance rule #6).
 * Furthermore, repeats the above with multiple different tessellation
 * value sets.
 *
 * The manner of primitive set comparison is defined by subclass. E.g.
 * case for invariance rule #1 tests that same vertices come out, in same
 * order; rule #5 only requires that the same triangles are output, but
 * not necessarily in the same order.
 *//*--------------------------------------------------------------------*/
class PrimitiveSetInvarianceCase : public TestCase
{
public:
    enum WindingUsage
    {
        WINDINGUSAGE_CCW = 0,
        WINDINGUSAGE_CW,
        WINDINGUSAGE_VARY,

        WINDINGUSAGE_LAST
    };

    PrimitiveSetInvarianceCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                               SpacingMode spacing, bool usePointMode, WindingUsage windingUsage)
        : TestCase(context, name, description)
        , m_primitiveType(primType)
        , m_spacing(spacing)
        , m_usePointMode(usePointMode)
        , m_windingUsage(windingUsage)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

protected:
    struct TessLevels
    {
        float inner[2];
        float outer[4];
        string description(void) const
        {
            return tessellationLevelsString(&inner[0], &outer[0]);
        }
    };
    struct LevelCase
    {
        vector<TessLevels> levels;
        int mem; //!< Subclass-defined arbitrary piece of data, for type of the levelcase, if needed. Passed to compare().
        LevelCase(const TessLevels &lev) : levels(vector<TessLevels>(1, lev)), mem(0)
        {
        }
        LevelCase(void) : mem(0)
        {
        }
    };

    virtual vector<LevelCase> genTessLevelCases(void) const;
    virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int levelCaseMem) const = 0;

    const TessPrimitiveType m_primitiveType;

private:
    struct Program
    {
        Winding winding;
        SharedPtr<const ShaderProgram> program;

        Program(Winding w, const SharedPtr<const ShaderProgram> &prog) : winding(w), program(prog)
        {
        }

        string description(void) const
        {
            return string() + "winding mode " + getWindingShaderName(winding);
        }
    };

    static const int RENDER_SIZE = 16;

    const SpacingMode m_spacing;
    const bool m_usePointMode;
    const WindingUsage m_windingUsage;

    vector<Program> m_programs;
};

vector<PrimitiveSetInvarianceCase::LevelCase> PrimitiveSetInvarianceCase::genTessLevelCases(void) const
{
    static const TessLevels basicTessLevelCases[] = {
        {{1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}}, {{63.0f, 24.0f}, {15.0f, 42.0f, 10.0f, 12.0f}},
        {{3.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{4.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
        {{2.0f, 2.0f}, {6.0f, 8.0f, 7.0f, 9.0f}}, {{5.0f, 6.0f}, {1.0f, 1.0f, 1.0f, 1.0f}},
        {{1.0f, 6.0f}, {2.0f, 3.0f, 1.0f, 4.0f}}, {{5.0f, 1.0f}, {2.0f, 3.0f, 1.0f, 4.0f}},
        {{5.2f, 1.6f}, {2.9f, 3.4f, 1.5f, 4.1f}}};

    vector<LevelCase> result;
    for (int i = 0; i < DE_LENGTH_OF_ARRAY(basicTessLevelCases); i++)
        result.push_back(LevelCase(basicTessLevelCases[i]));

    {
        de::Random rnd(123);
        for (int i = 0; i < 10; i++)
        {
            TessLevels levels;
            for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.inner); j++)
                levels.inner[j] = rnd.getFloat(1.0f, 16.0f);
            for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.outer); j++)
                levels.outer[j] = rnd.getFloat(1.0f, 16.0f);
            result.push_back(LevelCase(levels));
        }
    }

    return result;
}

void PrimitiveSetInvarianceCase::init(void)
{
    const int numDifferentConstantExprCases = 2;
    vector<Winding> windings;
    switch (m_windingUsage)
    {
    case WINDINGUSAGE_CCW:
        windings.push_back(WINDING_CCW);
        break;
    case WINDINGUSAGE_CW:
        windings.push_back(WINDING_CW);
        break;
    case WINDINGUSAGE_VARY:
        windings.push_back(WINDING_CCW);
        windings.push_back(WINDING_CW);
        break;
    default:
        DE_ASSERT(false);
    }

    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    for (int constantExprCaseNdx = 0; constantExprCaseNdx < numDifferentConstantExprCases; constantExprCaseNdx++)
    {
        for (int windingCaseNdx = 0; windingCaseNdx < (int)windings.size(); windingCaseNdx++)
        {
            const string floatLit01 = de::floatToString(10.0f / (float)(constantExprCaseNdx + 10), 2);
            const int programNdx    = (int)m_programs.size();

            std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                             "${GLSL_PER_VERTEX_OUT}\n"
                                             "\n"
                                             "in highp float in_v_attr;\n"
                                             "out highp float in_tc_attr;\n"
                                             "\n"
                                             "void main (void)\n"
                                             "{\n"
                                             "    in_tc_attr = in_v_attr;\n"
                                             "}\n");
            std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                                    "${TESSELLATION_SHADER_REQUIRE}\n"
                                                    "${GLSL_PER_VERTEX_IN_ARR}\n"
                                                    "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                                    "\n"
                                                    "layout (vertices = " +
                                                    de::toString(constantExprCaseNdx + 1) +
                                                    ") out;\n"
                                                    "\n"
                                                    "in highp float in_tc_attr[];\n"
                                                    "\n"
                                                    "patch out highp float in_te_positionOffset;\n"
                                                    "\n"
                                                    "void main (void)\n"
                                                    "{\n"
                                                    "    in_te_positionOffset = in_tc_attr[6];\n"
                                                    "\n"
                                                    "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
                                                    "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
                                                    "\n"
                                                    "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
                                                    "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
                                                    "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
                                                    "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
                                                    "}\n");
            std::string tessellationEvaluationTemplate(
                "${GLSL_VERSION_DECL}\n"
                "${TESSELLATION_SHADER_REQUIRE}\n"
                "${GLSL_PER_VERTEX_IN_ARR}\n"
                "${GLSL_PER_VERTEX_OUT}\n"
                "\n" +
                getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, windings[windingCaseNdx],
                                                        m_usePointMode) +
                "\n"
                "patch in highp float in_te_positionOffset;\n"
                "\n"
                "out highp vec4 in_f_color;\n"
                "invariant out highp vec3 out_te_tessCoord;\n"
                "\n"
                "void main (void)\n"
                "{\n"
                "    gl_Position = vec4(gl_TessCoord.xy*" +
                floatLit01 +
                " - in_te_positionOffset + float(gl_PrimitiveID)*0.1, 0.0, 1.0);\n"
                "    in_f_color = vec4(" +
                floatLit01 +
                ");\n"
                "    out_te_tessCoord = gl_TessCoord;\n"
                "}\n");
            std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                               "\n"
                                               "layout (location = 0) out mediump vec4 o_color;\n"
                                               "\n"
                                               "in highp vec4 in_f_color;\n"
                                               "\n"
                                               "void main (void)\n"
                                               "{\n"
                                               "    o_color = in_f_color;\n"
                                               "}\n");

            m_programs.push_back(
                Program(windings[windingCaseNdx],
                        SharedPtr<const ShaderProgram>(new ShaderProgram(
                            m_context.getRenderContext(),
                            glu::ProgramSources()
                                << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                                << glu::TessellationControlSource(
                                       specializeShader(m_context, tessellationControlTemplate.c_str()))
                                << glu::TessellationEvaluationSource(
                                       specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                                << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                                << glu::TransformFeedbackVarying("out_te_tessCoord")
                                << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)))));

            {
                const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx),
                                                    "Program " + de::toString(programNdx));

                if (programNdx == 0 || !m_programs.back().program->isOk())
                    m_testCtx.getLog() << *m_programs.back().program;

                if (!m_programs.back().program->isOk())
                    TCU_FAIL("Program compilation failed");

                if (programNdx > 0)
                    m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx
                                       << " is similar to above, except some constants are different, and: "
                                       << m_programs.back().description() << TestLog::EndMessage;
            }
        }
    }
}

void PrimitiveSetInvarianceCase::deinit(void)
{
    m_programs.clear();
}

PrimitiveSetInvarianceCase::IterateResult PrimitiveSetInvarianceCase::iterate(void)
{
    typedef TransformFeedbackHandler<Vec3> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl               = renderCtx.getFunctions();
    const vector<LevelCase> tessLevelCases = genTessLevelCases();
    vector<vector<int>> primitiveCounts;
    int maxNumPrimitives = -1;

    for (int caseNdx = 0; caseNdx < (int)tessLevelCases.size(); caseNdx++)
    {
        primitiveCounts.push_back(vector<int>());
        for (int i = 0; i < (int)tessLevelCases[caseNdx].levels.size(); i++)
        {
            const int primCount = referencePrimitiveCount(m_primitiveType, m_spacing, m_usePointMode,
                                                          &tessLevelCases[caseNdx].levels[i].inner[0],
                                                          &tessLevelCases[caseNdx].levels[i].outer[0]);
            primitiveCounts.back().push_back(primCount);
            maxNumPrimitives = de::max(maxNumPrimitives, primCount);
        }
    }

    const uint32_t primitiveTypeGL = outputPrimitiveTypeGL(m_primitiveType, m_usePointMode);
    const TFHandler transformFeedback(m_context.getRenderContext(),
                                      2 * maxNumPrimitives * numVerticesPerPrimitive(primitiveTypeGL));

    setViewport(gl, viewport);
    gl.patchParameteri(GL_PATCH_VERTICES, 7);

    for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
    {
        const LevelCase &levelCase = tessLevelCases[tessLevelCaseNdx];
        vector<Vec3> firstPrimVertices;

        {
            string tessLevelsStr;
            for (int i = 0; i < (int)levelCase.levels.size(); i++)
                tessLevelsStr += (levelCase.levels.size() > 1 ? "\n" : "") + levelCase.levels[i].description();
            log << TestLog::Message << "Tessellation level sets: " << tessLevelsStr << TestLog::EndMessage;
        }

        for (int subTessLevelCaseNdx = 0; subTessLevelCaseNdx < (int)levelCase.levels.size(); subTessLevelCaseNdx++)
        {
            const TessLevels &tessLevels = levelCase.levels[subTessLevelCaseNdx];
            const float(&inner)[2]       = tessLevels.inner;
            const float(&outer)[4]       = tessLevels.outer;
            const float attribute[2 * 7] = {inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.0f,
                                            inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.5f};
            const glu::VertexArrayBinding bindings[] = {
                glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attribute), 0, &attribute[0])};

            for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++)
            {
                const uint32_t programGL = m_programs[programNdx].program->getProgram();
                gl.useProgram(programGL);
                const TFHandler::Result tfResult =
                    transformFeedback.renderAndGetPrimitives(programGL, primitiveTypeGL, DE_LENGTH_OF_ARRAY(bindings),
                                                             &bindings[0], DE_LENGTH_OF_ARRAY(attribute));

                if (tfResult.numPrimitives != 2 * primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx])
                {
                    log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be "
                        << tfResult.numPrimitives << ", reference value is "
                        << 2 * primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx] << TestLog::EndMessage;

                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
                    return STOP;
                }

                {
                    const int half = (int)tfResult.varying.size() / 2;
                    const vector<Vec3> prim0Vertices =
                        vector<Vec3>(tfResult.varying.begin(), tfResult.varying.begin() + half);
                    const Vec3 *const prim1Vertices = &tfResult.varying[half];

                    for (int vtxNdx = 0; vtxNdx < (int)prim0Vertices.size(); vtxNdx++)
                    {
                        if (prim0Vertices[vtxNdx] != prim1Vertices[vtxNdx])
                        {
                            log << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx
                                << " differs between two primitives drawn in one draw call" << TestLog::EndMessage
                                << TestLog::Message << "Note: the coordinate is " << prim0Vertices[vtxNdx]
                                << " for the first primitive and " << prim1Vertices[vtxNdx] << " for the second"
                                << TestLog::EndMessage << TestLog::Message
                                << "Note: tessellation levels for both primitives were: "
                                << tessellationLevelsString(&inner[0], &outer[0]) << TestLog::EndMessage;
                            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
                            return STOP;
                        }
                    }

                    if (programNdx == 0 && subTessLevelCaseNdx == 0)
                        firstPrimVertices = prim0Vertices;
                    else
                    {
                        const bool compareOk = compare(firstPrimVertices, prim0Vertices, levelCase.mem);
                        if (!compareOk)
                        {
                            log << TestLog::Message
                                << "Note: comparison of tessellation coordinates failed; comparison was made between "
                                   "following cases:\n"
                                << "  - case A: program 0, tessellation levels: "
                                << tessLevelCases[tessLevelCaseNdx].levels[0].description() << "\n"
                                << "  - case B: program " << programNdx
                                << ", tessellation levels: " << tessLevels.description() << TestLog::EndMessage;
                            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
                            return STOP;
                        }
                    }
                }
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #1
 *
 * Test that the sequence of primitives input to the TES only depends on
 * the tessellation levels, tessellation mode, spacing mode, winding, and
 * point mode.
 *//*--------------------------------------------------------------------*/
class InvariantPrimitiveSetCase : public PrimitiveSetInvarianceCase
{
public:
    InvariantPrimitiveSetCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                              SpacingMode spacing, Winding winding, bool usePointMode)
        : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, usePointMode,
                                     winding == WINDING_CCW ? WINDINGUSAGE_CCW :
                                     winding == WINDING_CW  ? WINDINGUSAGE_CW :
                                                              WINDINGUSAGE_LAST)
    {
    }

protected:
    virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int) const
    {
        for (int vtxNdx = 0; vtxNdx < (int)coordsA.size(); vtxNdx++)
        {
            if (coordsA[vtxNdx] != coordsB[vtxNdx])
            {
                m_testCtx.getLog() << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx
                                   << " differs between two programs" << TestLog::EndMessage << TestLog::Message
                                   << "Note: the coordinate is " << coordsA[vtxNdx] << " for the first program and "
                                   << coordsB[vtxNdx] << " for the other" << TestLog::EndMessage;
                return false;
            }
        }
        return true;
    }
};

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #2
 *
 * Test that the set of vertices along an outer edge of a quad or triangle
 * only depends on that edge's tessellation level, and spacing.
 *
 * For each (outer) edge in the quad or triangle, draw multiple patches
 * with identical tessellation levels for that outer edge but with
 * different values for the other outer edges; compare, among the
 * primitives, the vertices generated for that outer edge. Repeat with
 * different programs, using different winding etc. settings. Compare
 * the edge's vertices between different programs.
 *//*--------------------------------------------------------------------*/
class InvariantOuterEdgeCase : public TestCase
{
public:
    InvariantOuterEdgeCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                           SpacingMode spacing)
        : TestCase(context, name, description)
        , m_primitiveType(primType)
        , m_spacing(spacing)
    {
        DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    struct Program
    {
        Winding winding;
        bool usePointMode;
        SharedPtr<const ShaderProgram> program;

        Program(Winding w, bool point, const SharedPtr<const ShaderProgram> &prog)
            : winding(w)
            , usePointMode(point)
            , program(prog)
        {
        }

        string description(void) const
        {
            return string() + "winding mode " + getWindingShaderName(winding) + ", " + (usePointMode ? "" : "don't ") +
                   "use point mode";
        }
    };

    static vector<float> generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, float constantOuterLevel);

    static const int RENDER_SIZE = 16;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;

    vector<Program> m_programs;
};

vector<float> InvariantOuterEdgeCase::generatePatchTessLevels(int numPatches, int constantOuterLevelIndex,
                                                              float constantOuterLevel)
{
    de::Random rnd(123);
    return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
}

void InvariantOuterEdgeCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    for (int windingI = 0; windingI < WINDING_LAST; windingI++)
    {
        const Winding winding = (Winding)windingI;

        for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
        {
            const bool usePointMode = usePointModeI != 0;
            const int programNdx    = (int)m_programs.size();
            const string floatLit01 = de::floatToString(10.0f / (float)(programNdx + 10), 2);

            std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                             "${GLSL_PER_VERTEX_OUT}\n"
                                             "\n"
                                             "in highp float in_v_attr;\n"
                                             "out highp float in_tc_attr;\n"
                                             "\n"
                                             "void main (void)\n"
                                             "{\n"
                                             "    in_tc_attr = in_v_attr;\n"
                                             "}\n");
            std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                                    "${TESSELLATION_SHADER_REQUIRE}\n"
                                                    "${GLSL_PER_VERTEX_IN_ARR}\n"
                                                    "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                                    "\n"
                                                    "layout (vertices = " +
                                                    de::toString(programNdx + 1) +
                                                    ") out;\n"
                                                    "\n"
                                                    "in highp float in_tc_attr[];\n"
                                                    "\n"
                                                    "void main (void)\n"
                                                    "{\n"
                                                    "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
                                                    "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
                                                    "\n"
                                                    "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
                                                    "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
                                                    "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
                                                    "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
                                                    "}\n");
            std::string tessellationEvaluationTemplate(
                "${GLSL_VERSION_DECL}\n"
                "${TESSELLATION_SHADER_REQUIRE}\n"
                "${GLSL_PER_VERTEX_IN_ARR}\n"
                "${GLSL_PER_VERTEX_OUT}\n"
                "\n" +
                getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, winding, usePointMode) +
                "\n"
                "out highp vec4 in_f_color;\n"
                "invariant out highp vec3 out_te_tessCoord;\n"
                "\n"
                "void main (void)\n"
                "{\n"
                "    gl_Position = vec4(gl_TessCoord.xy*" +
                floatLit01 +
                " - float(gl_PrimitiveID)*0.05, 0.0, 1.0);\n"
                "    in_f_color = vec4(" +
                floatLit01 +
                ");\n"
                "    out_te_tessCoord = gl_TessCoord;\n"
                "}\n");
            std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                               "\n"
                                               "layout (location = 0) out mediump vec4 o_color;\n"
                                               "\n"
                                               "in highp vec4 in_f_color;\n"
                                               "\n"
                                               "void main (void)\n"
                                               "{\n"
                                               "    o_color = in_f_color;\n"
                                               "}\n");

            m_programs.push_back(
                Program(winding, usePointMode,
                        SharedPtr<const ShaderProgram>(new ShaderProgram(
                            m_context.getRenderContext(),
                            glu::ProgramSources()
                                << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                                << glu::TessellationControlSource(
                                       specializeShader(m_context, tessellationControlTemplate.c_str()))
                                << glu::TessellationEvaluationSource(
                                       specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                                << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                                << glu::TransformFeedbackVarying("out_te_tessCoord")
                                << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)))));

            {
                const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx),
                                                    "Program " + de::toString(programNdx));

                if (programNdx == 0 || !m_programs.back().program->isOk())
                    m_testCtx.getLog() << *m_programs.back().program;

                if (!m_programs.back().program->isOk())
                    TCU_FAIL("Program compilation failed");

                if (programNdx > 0)
                    m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx
                                       << " is similar to above, except some constants are different, and: "
                                       << m_programs.back().description() << TestLog::EndMessage;
            }
        }
    }
}

void InvariantOuterEdgeCase::deinit(void)
{
    m_programs.clear();
}

InvariantOuterEdgeCase::IterateResult InvariantOuterEdgeCase::iterate(void)
{
    typedef TransformFeedbackHandler<Vec3> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl = renderCtx.getFunctions();

    static const float singleOuterEdgeLevels[]          = {1.0f, 1.2f,  1.9f, 2.3f,  2.8f,  3.3f,
                                                           3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f};
    const int numPatchesPerDrawCall                     = 10;
    const vector<OuterEdgeDescription> edgeDescriptions = outerEdgeDescriptions(m_primitiveType);

    {
        // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
        int maxNumVerticesInDrawCall = 0;
        {
            const vector<float> patchTessLevels =
                generatePatchTessLevels(numPatchesPerDrawCall, 0 /* outer-edge index doesn't affect vertex count */,
                                        arrayMax(singleOuterEdgeLevels));

            for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
                maxNumVerticesInDrawCall =
                    de::max(maxNumVerticesInDrawCall,
                            multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, usePointModeI != 0,
                                                              &patchTessLevels[0], numPatchesPerDrawCall));
        }

        {
            const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

            setViewport(gl, viewport);
            gl.patchParameteri(GL_PATCH_VERTICES, 6);

            for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
            {
                const OuterEdgeDescription &edgeDesc = edgeDescriptions[outerEdgeIndex];

                for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels);
                     outerEdgeLevelCaseNdx++)
                {
                    typedef std::set<Vec3, VecLexLessThan<3>> Vec3Set;

                    const vector<float> patchTessLevels = generatePatchTessLevels(
                        numPatchesPerDrawCall, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
                    const glu::VertexArrayBinding bindings[] = {
                        glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0])};
                    Vec3Set
                        firstOuterEdgeVertices; // Vertices of the outer edge of the first patch of the first program's draw call; used for comparison with other patches.

                    log << TestLog::Message << "Testing with outer tessellation level "
                        << singleOuterEdgeLevels[outerEdgeLevelCaseNdx] << " for the " << edgeDesc.description()
                        << " edge, and with various levels for other edges, and with all programs"
                        << TestLog::EndMessage;

                    for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++)
                    {
                        const Program &program   = m_programs[programNdx];
                        const uint32_t programGL = program.program->getProgram();

                        gl.useProgram(programGL);

                        {
                            const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(
                                programGL, outputPrimitiveTypeGL(m_primitiveType, program.usePointMode),
                                DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
                            const int refNumVertices =
                                multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, program.usePointMode,
                                                                  &patchTessLevels[0], numPatchesPerDrawCall);
                            int numVerticesRead = 0;

                            if ((int)tfResult.varying.size() != refNumVertices)
                            {
                                log << TestLog::Message
                                    << "Failure: the number of vertices returned by transform feedback is "
                                    << tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
                                    << TestLog::Message << "Note: rendered " << numPatchesPerDrawCall
                                    << " patches in one draw call; tessellation levels for each patch are (in order "
                                       "[inner0, inner1, outer0, outer1, outer2, outer3]):\n"
                                    << containerStr(patchTessLevels, 6) << TestLog::EndMessage;

                                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                                return STOP;
                            }

                            // Check the vertices of each patch.

                            for (int patchNdx = 0; patchNdx < numPatchesPerDrawCall; patchNdx++)
                            {
                                const float *const innerLevels = &patchTessLevels[6 * patchNdx + 0];
                                const float *const outerLevels = &patchTessLevels[6 * patchNdx + 2];
                                const int patchNumVertices     = referenceVertexCount(
                                    m_primitiveType, m_spacing, program.usePointMode, innerLevels, outerLevels);
                                Vec3Set outerEdgeVertices;

                                // We're interested in just the vertices on the current outer edge.
                                for (int vtxNdx = numVerticesRead; vtxNdx < numVerticesRead + patchNumVertices;
                                     vtxNdx++)
                                {
                                    const Vec3 &vtx = tfResult.varying[vtxNdx];
                                    if (edgeDesc.contains(vtx))
                                        outerEdgeVertices.insert(tfResult.varying[vtxNdx]);
                                }

                                // Check that the outer edge contains an appropriate number of vertices.
                                {
                                    const int refNumVerticesOnOuterEdge =
                                        1 + getClampedRoundedTessLevel(m_spacing,
                                                                       singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);

                                    if ((int)outerEdgeVertices.size() != refNumVerticesOnOuterEdge)
                                    {
                                        log << TestLog::Message << "Failure: the number of vertices on outer edge is "
                                            << outerEdgeVertices.size() << ", expected " << refNumVerticesOnOuterEdge
                                            << TestLog::EndMessage << TestLog::Message
                                            << "Note: vertices on the outer edge are:\n"
                                            << containerStr(outerEdgeVertices, 5, 0) << TestLog::EndMessage
                                            << TestLog::Message
                                            << "Note: the following parameters were used: " << program.description()
                                            << ", tessellation levels: "
                                            << tessellationLevelsString(innerLevels, outerLevels)
                                            << TestLog::EndMessage;
                                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                                        return STOP;
                                    }
                                }

                                // Compare the vertices to those of the first patch (unless this is the first patch).

                                if (programNdx == 0 && patchNdx == 0)
                                    firstOuterEdgeVertices = outerEdgeVertices;
                                else
                                {
                                    if (firstOuterEdgeVertices != outerEdgeVertices)
                                    {
                                        log << TestLog::Message
                                            << "Failure: vertices generated for the edge differ between the following "
                                               "cases:\n"
                                            << "  - case A: " << m_programs[0].description()
                                            << ", tessellation levels: "
                                            << tessellationLevelsString(&patchTessLevels[0], &patchTessLevels[2])
                                            << "\n"
                                            << "  - case B: " << program.description() << ", tessellation levels: "
                                            << tessellationLevelsString(innerLevels, outerLevels)
                                            << TestLog::EndMessage;

                                        log << TestLog::Message
                                            << "Note: resulting vertices for the edge for the cases were:\n"
                                            << "  - case A: " << containerStr(firstOuterEdgeVertices, 5, 14) << "\n"
                                            << "  - case B: " << containerStr(outerEdgeVertices, 5, 14)
                                            << TestLog::EndMessage;

                                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                                        return STOP;
                                    }
                                }

                                numVerticesRead += patchNumVertices;
                            }

                            DE_ASSERT(numVerticesRead == (int)tfResult.varying.size());
                        }
                    }
                }
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #3
 *
 * Test that the vertices along an outer edge are placed symmetrically.
 *
 * Draw multiple patches with different tessellation levels and different
 * point_mode, winding etc. Before outputting tesscoords with TF, mirror
 * the vertices in the TES such that every vertex on an outer edge -
 * except the possible middle vertex - should be duplicated in the output.
 * Check that appropriate duplicates exist.
 *//*--------------------------------------------------------------------*/
class SymmetricOuterEdgeCase : public TestCase
{
public:
    SymmetricOuterEdgeCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                           SpacingMode spacing, Winding winding, bool usePointMode)
        : TestCase(context, name, description)
        , m_primitiveType(primType)
        , m_spacing(spacing)
        , m_winding(winding)
        , m_usePointMode(usePointMode)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static vector<float> generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, float constantOuterLevel);

    static const int RENDER_SIZE = 16;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;
    const Winding m_winding;
    const bool m_usePointMode;

    SharedPtr<const glu::ShaderProgram> m_program;
};

vector<float> SymmetricOuterEdgeCase::generatePatchTessLevels(int numPatches, int constantOuterLevelIndex,
                                                              float constantOuterLevel)
{
    de::Random rnd(123);
    return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
}

void SymmetricOuterEdgeCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "in highp float in_v_attr;\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = 1) out;\n"
                                            "\n"
                                            "in highp float in_tc_attr[];\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
                                            "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
                                            "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
                                            "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
                                            "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
        "\n"
        "out highp vec4 in_f_color;\n"
        "out highp vec4 out_te_tessCoord_isMirrored;\n"
        "\n"
        "void main (void)\n"
        "{\n" +
        (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
             "    float x = gl_TessCoord.x;\n"
             "    float y = gl_TessCoord.y;\n"
             "    float z = gl_TessCoord.z;\n"
             "    // Mirror one half of each outer edge onto the other half, except the endpoints (because they belong "
             "to two edges)\n"
             "    out_te_tessCoord_isMirrored = z == 0.0 && x > 0.5 && x != 1.0 ? vec4(1.0-x,  1.0-y,    0.0, 1.0)\n"
             "                                : y == 0.0 && z > 0.5 && z != 1.0 ? vec4(1.0-x,    0.0,  1.0-z, 1.0)\n"
             "                                : x == 0.0 && y > 0.5 && y != 1.0 ? vec4(  0.0,  1.0-y,  1.0-z, 1.0)\n"
             "                                : vec4(x, y, z, 0.0);\n" :
         m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
             "    float x = gl_TessCoord.x;\n"
             "    float y = gl_TessCoord.y;\n"
             "    // Mirror one half of each outer edge onto the other half, except the endpoints (because they belong "
             "to two edges)\n"
             "    out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 && y != 1.0 ? vec4(    x, 1.0-y, "
             "0.0, "
             "1.0)\n"
             "                                : (y == 0.0 || y == 1.0) && x > 0.5 && x != 1.0 ? vec4(1.0-x,     y, "
             "0.0, "
             "1.0)\n"
             "                                : vec4(x, y, 0.0, 0.0);\n" :
         m_primitiveType == TESSPRIMITIVETYPE_ISOLINES ?
             "    float x = gl_TessCoord.x;\n"
             "    float y = gl_TessCoord.y;\n"
             "    // Mirror one half of each outer edge onto the other half\n"
             "    out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 ? vec4(x, 1.0-y, 0.0, 1.0)\n"
             "                                : vec4(x, y, 0.0, 0.0f);\n" :
             DE_NULL) +
        "\n"
        "    gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
        "    in_f_color = vec4(1.0);\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in highp vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                              << glu::TransformFeedbackVarying("out_te_tessCoord_isMirrored")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void SymmetricOuterEdgeCase::deinit(void)
{
    m_program.clear();
}

SymmetricOuterEdgeCase::IterateResult SymmetricOuterEdgeCase::iterate(void)
{
    typedef TransformFeedbackHandler<Vec4> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl = renderCtx.getFunctions();

    static const float singleOuterEdgeLevels[]          = {1.0f, 1.2f,  1.9f, 2.3f,  2.8f,  3.3f,
                                                           3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f};
    const vector<OuterEdgeDescription> edgeDescriptions = outerEdgeDescriptions(m_primitiveType);

    {
        // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
        int maxNumVerticesInDrawCall;
        {
            const vector<float> patchTessLevels = generatePatchTessLevels(
                1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));
            maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode,
                                                            &patchTessLevels[0], &patchTessLevels[2]);
        }

        {
            const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

            setViewport(gl, viewport);
            gl.patchParameteri(GL_PATCH_VERTICES, 6);

            for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
            {
                const OuterEdgeDescription &edgeDesc = edgeDescriptions[outerEdgeIndex];

                for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels);
                     outerEdgeLevelCaseNdx++)
                {
                    typedef std::set<Vec3, VecLexLessThan<3>> Vec3Set;

                    const vector<float> patchTessLevels =
                        generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
                    const glu::VertexArrayBinding bindings[] = {
                        glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0])};

                    log << TestLog::Message << "Testing with outer tessellation level "
                        << singleOuterEdgeLevels[outerEdgeLevelCaseNdx] << " for the " << edgeDesc.description()
                        << " edge, and with various levels for other edges" << TestLog::EndMessage;

                    {
                        const uint32_t programGL = m_program->getProgram();

                        gl.useProgram(programGL);

                        {
                            const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(
                                programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
                                DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
                            const int refNumVertices = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode,
                                                                            &patchTessLevels[0], &patchTessLevels[2]);

                            if ((int)tfResult.varying.size() != refNumVertices)
                            {
                                log << TestLog::Message
                                    << "Failure: the number of vertices returned by transform feedback is "
                                    << tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
                                    << TestLog::Message
                                    << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, "
                                       "outer0, outer1, outer2, outer3]):\n"
                                    << containerStr(patchTessLevels, 6) << TestLog::EndMessage;

                                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                                return STOP;
                            }

                            // Check the vertices.

                            {
                                Vec3Set nonMirroredEdgeVertices;
                                Vec3Set mirroredEdgeVertices;

                                // We're interested in just the vertices on the current outer edge.
                                for (int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++)
                                {
                                    const Vec3 &vtx = tfResult.varying[vtxNdx].swizzle(0, 1, 2);
                                    if (edgeDesc.contains(vtx))
                                    {
                                        // Ignore the middle vertex of the outer edge, as it's exactly at the mirroring point;
                                        // for isolines, also ignore (0, 0) and (1, 0) because there's no mirrored counterpart for them.
                                        if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES &&
                                            vtx == tcu::select(Vec3(0.0f), Vec3(0.5f),
                                                               singleTrueMask<3>(edgeDesc.constantCoordinateIndex)))
                                            continue;
                                        if (m_primitiveType == TESSPRIMITIVETYPE_QUADS &&
                                            vtx.swizzle(0, 1) ==
                                                tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]),
                                                            Vec2(0.5f),
                                                            singleTrueMask<2>(edgeDesc.constantCoordinateIndex)))
                                            continue;
                                        if (m_primitiveType == TESSPRIMITIVETYPE_ISOLINES &&
                                            (vtx == Vec3(0.0f, 0.5f, 0.0f) || vtx == Vec3(1.0f, 0.5f, 0.0f) ||
                                             vtx == Vec3(0.0f, 0.0f, 0.0f) || vtx == Vec3(1.0f, 0.0f, 0.0f)))
                                            continue;

                                        const bool isMirrored = tfResult.varying[vtxNdx].w() > 0.5f;
                                        if (isMirrored)
                                            mirroredEdgeVertices.insert(vtx);
                                        else
                                            nonMirroredEdgeVertices.insert(vtx);
                                    }
                                }

                                if (m_primitiveType != TESSPRIMITIVETYPE_ISOLINES)
                                {
                                    // Check that both endpoints are present. Note that endpoints aren't mirrored by the shader, since they belong to more than one edge.

                                    Vec3 endpointA;
                                    Vec3 endpointB;

                                    if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
                                    {
                                        endpointA =
                                            tcu::select(Vec3(1.0f), Vec3(0.0f),
                                                        singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 1) % 3));
                                        endpointB =
                                            tcu::select(Vec3(1.0f), Vec3(0.0f),
                                                        singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 2) % 3));
                                    }
                                    else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
                                    {
                                        endpointA.xy() =
                                            tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(0.0f),
                                                        singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
                                        endpointB.xy() =
                                            tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(1.0f),
                                                        singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
                                    }
                                    else
                                        DE_ASSERT(false);

                                    if (!contains(nonMirroredEdgeVertices, endpointA) ||
                                        !contains(nonMirroredEdgeVertices, endpointB))
                                    {
                                        log << TestLog::Message << "Failure: edge doesn't contain both endpoints, "
                                            << endpointA << " and " << endpointB << TestLog::EndMessage
                                            << TestLog::Message << "Note: non-mirrored vertices:\n"
                                            << containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n"
                                            << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage;
                                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                                        return STOP;
                                    }
                                    nonMirroredEdgeVertices.erase(endpointA);
                                    nonMirroredEdgeVertices.erase(endpointB);
                                }

                                if (nonMirroredEdgeVertices != mirroredEdgeVertices)
                                {
                                    log << TestLog::Message
                                        << "Failure: the set of mirrored edges isn't equal to the set of non-mirrored "
                                           "edges (ignoring endpoints and possible middle)"
                                        << TestLog::EndMessage << TestLog::Message << "Note: non-mirrored vertices:\n"
                                        << containerStr(nonMirroredEdgeVertices, 5) << "\nmirrored vertices:\n"
                                        << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage;
                                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                                    return STOP;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #4
 *
 * Test that the vertices on an outer edge don't depend on which of the
 * edges it is, other than with respect to component order.
 *//*--------------------------------------------------------------------*/
class OuterEdgeVertexSetIndexIndependenceCase : public TestCase
{
public:
    OuterEdgeVertexSetIndexIndependenceCase(Context &context, const char *name, const char *description,
                                            TessPrimitiveType primType, SpacingMode spacing, Winding winding,
                                            bool usePointMode)
        : TestCase(context, name, description)
        , m_primitiveType(primType)
        , m_spacing(spacing)
        , m_winding(winding)
        , m_usePointMode(usePointMode)
    {
        DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static vector<float> generatePatchTessLevels(int numPatches, int constantOuterLevelIndex, float constantOuterLevel);

    static const int RENDER_SIZE = 16;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;
    const Winding m_winding;
    const bool m_usePointMode;

    SharedPtr<const glu::ShaderProgram> m_program;
};

vector<float> OuterEdgeVertexSetIndexIndependenceCase::generatePatchTessLevels(int numPatches,
                                                                               int constantOuterLevelIndex,
                                                                               float constantOuterLevel)
{
    de::Random rnd(123);
    return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
}

void OuterEdgeVertexSetIndexIndependenceCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp float in_v_attr;\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = 1) out;\n"
                                            "\n"
                                            "in highp float in_tc_attr[];\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
                                            "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
                                            "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
                                            "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
                                            "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
        "\n"
        "out highp vec4 in_f_color;\n"
        "out highp vec3 out_te_tessCoord;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    out_te_tessCoord = gl_TessCoord;"
        "    gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
        "    in_f_color = vec4(1.0);\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in highp vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                              << glu::TransformFeedbackVarying("out_te_tessCoord")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void OuterEdgeVertexSetIndexIndependenceCase::deinit(void)
{
    m_program.clear();
}

OuterEdgeVertexSetIndexIndependenceCase::IterateResult OuterEdgeVertexSetIndexIndependenceCase::iterate(void)
{
    typedef TransformFeedbackHandler<Vec3> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl = renderCtx.getFunctions();
    const uint32_t programGL = m_program->getProgram();

    static const float singleOuterEdgeLevels[]          = {1.0f, 1.2f,  1.9f, 2.3f,  2.8f,  3.3f,
                                                           3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f};
    const vector<OuterEdgeDescription> edgeDescriptions = outerEdgeDescriptions(m_primitiveType);

    gl.useProgram(programGL);
    setViewport(gl, viewport);
    gl.patchParameteri(GL_PATCH_VERTICES, 6);

    {
        // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
        int maxNumVerticesInDrawCall = 0;
        {
            const vector<float> patchTessLevels = generatePatchTessLevels(
                1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));
            maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode,
                                                            &patchTessLevels[0], &patchTessLevels[2]);
        }

        {
            const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

            for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels);
                 outerEdgeLevelCaseNdx++)
            {
                typedef std::set<Vec3, VecLexLessThan<3>> Vec3Set;

                Vec3Set firstEdgeVertices;

                for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
                {
                    const OuterEdgeDescription &edgeDesc = edgeDescriptions[outerEdgeIndex];
                    const vector<float> patchTessLevels =
                        generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
                    const glu::VertexArrayBinding bindings[] = {
                        glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0])};

                    log << TestLog::Message << "Testing with outer tessellation level "
                        << singleOuterEdgeLevels[outerEdgeLevelCaseNdx] << " for the " << edgeDesc.description()
                        << " edge, and with various levels for other edges" << TestLog::EndMessage;

                    {
                        const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(
                            programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
                            DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
                        const int refNumVertices = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode,
                                                                        &patchTessLevels[0], &patchTessLevels[2]);

                        if ((int)tfResult.varying.size() != refNumVertices)
                        {
                            log << TestLog::Message
                                << "Failure: the number of vertices returned by transform feedback is "
                                << tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
                                << TestLog::Message
                                << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, outer0, "
                                   "outer1, outer2, outer3]):\n"
                                << containerStr(patchTessLevels, 6) << TestLog::EndMessage;

                            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                            return STOP;
                        }

                        {
                            Vec3Set currentEdgeVertices;

                            // Get the vertices on the current outer edge.
                            for (int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++)
                            {
                                const Vec3 &vtx = tfResult.varying[vtxNdx];
                                if (edgeDesc.contains(vtx))
                                {
                                    // Swizzle components to match the order of the first edge.
                                    if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
                                    {
                                        currentEdgeVertices.insert(outerEdgeIndex == 0 ? vtx :
                                                                   outerEdgeIndex == 1 ? vtx.swizzle(1, 0, 2) :
                                                                   outerEdgeIndex == 2 ? vtx.swizzle(2, 1, 0) :
                                                                                         Vec3(-1.0f));
                                    }
                                    else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
                                    {
                                        currentEdgeVertices.insert(Vec3(outerEdgeIndex == 0 ? vtx.y() :
                                                                        outerEdgeIndex == 1 ? vtx.x() :
                                                                        outerEdgeIndex == 2 ? vtx.y() :
                                                                        outerEdgeIndex == 3 ? vtx.x() :
                                                                                              -1.0f,
                                                                        0.0f, 0.0f));
                                    }
                                    else
                                        DE_ASSERT(false);
                                }
                            }

                            if (outerEdgeIndex == 0)
                                firstEdgeVertices = currentEdgeVertices;
                            else
                            {
                                // Compare vertices of this edge to those of the first edge.

                                if (currentEdgeVertices != firstEdgeVertices)
                                {
                                    const char *const swizzleDesc = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
                                                                        (outerEdgeIndex == 1 ? "(y, x, z)" :
                                                                         outerEdgeIndex == 2 ? "(z, y, x)" :
                                                                                               DE_NULL) :
                                                                    m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
                                                                        (outerEdgeIndex == 1 ? "(x, 0)" :
                                                                         outerEdgeIndex == 2 ? "(y, 0)" :
                                                                         outerEdgeIndex == 3 ? "(x, 0)" :
                                                                                               DE_NULL) :
                                                                        DE_NULL;

                                    log << TestLog::Message << "Failure: the set of vertices on the "
                                        << edgeDesc.description() << " edge"
                                        << " doesn't match the set of vertices on the "
                                        << edgeDescriptions[0].description() << " edge" << TestLog::EndMessage
                                        << TestLog::Message << "Note: set of vertices on " << edgeDesc.description()
                                        << " edge, components swizzled like " << swizzleDesc
                                        << " to match component order on first edge:\n"
                                        << containerStr(currentEdgeVertices, 5) << "\non "
                                        << edgeDescriptions[0].description() << " edge:\n"
                                        << containerStr(firstEdgeVertices, 5) << TestLog::EndMessage;
                                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
                                    return STOP;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #5
 *
 * Test that the set of triangles input to the TES only depends on the
 * tessellation levels, tessellation mode and spacing mode. Specifically,
 * winding doesn't change the set of triangles, though it can change the
 * order in which they are input to TES, and can (and will) change the
 * vertex order within a triangle.
 *//*--------------------------------------------------------------------*/
class InvariantTriangleSetCase : public PrimitiveSetInvarianceCase
{
public:
    InvariantTriangleSetCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                             SpacingMode spacing)
        : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
    {
        DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
    }

protected:
    virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int) const
    {
        return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog());
    }
};

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #6
 *
 * Test that the set of inner triangles input to the TES only depends on
 * the inner tessellation levels, tessellation mode and spacing mode.
 *//*--------------------------------------------------------------------*/
class InvariantInnerTriangleSetCase : public PrimitiveSetInvarianceCase
{
public:
    InvariantInnerTriangleSetCase(Context &context, const char *name, const char *description,
                                  TessPrimitiveType primType, SpacingMode spacing)
        : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
    {
        DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
    }

protected:
    virtual vector<LevelCase> genTessLevelCases(void) const
    {
        const int numSubCases               = 4;
        const vector<LevelCase> baseResults = PrimitiveSetInvarianceCase::genTessLevelCases();
        vector<LevelCase> result;
        de::Random rnd(123);

        // Generate variants with different values for irrelevant levels.
        for (int baseNdx = 0; baseNdx < (int)baseResults.size(); baseNdx++)
        {
            const TessLevels &base = baseResults[baseNdx].levels[0];
            TessLevels levels      = base;
            LevelCase levelCase;

            for (int subNdx = 0; subNdx < numSubCases; subNdx++)
            {
                levelCase.levels.push_back(levels);

                for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++)
                    levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
                if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
                    levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
            }

            result.push_back(levelCase);
        }

        return result;
    }

    struct IsInnerTriangleTriangle
    {
        bool operator()(const Vec3 *vertices) const
        {
            for (int v = 0; v < 3; v++)
                for (int c = 0; c < 3; c++)
                    if (vertices[v][c] == 0.0f)
                        return false;
            return true;
        }
    };

    struct IsInnerQuadTriangle
    {
        bool operator()(const Vec3 *vertices) const
        {
            for (int v = 0; v < 3; v++)
                for (int c = 0; c < 2; c++)
                    if (vertices[v][c] == 0.0f || vertices[v][c] == 1.0f)
                        return false;
            return true;
        }
    };

    virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int) const
    {
        if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
            return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerTriangleTriangle(),
                                       "outer triangles");
        else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
            return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerQuadTriangle(), "outer triangles");
        else
        {
            DE_ASSERT(false);
            return false;
        }
    }
};

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #7
 *
 * Test that the set of outer triangles input to the TES only depends on
 * tessellation mode, spacing mode and the inner and outer tessellation
 * levels corresponding to the inner and outer edges relevant to that
 * triangle.
 *//*--------------------------------------------------------------------*/
class InvariantOuterTriangleSetCase : public PrimitiveSetInvarianceCase
{
public:
    InvariantOuterTriangleSetCase(Context &context, const char *name, const char *description,
                                  TessPrimitiveType primType, SpacingMode spacing)
        : PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
    {
        DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
    }

protected:
    virtual vector<LevelCase> genTessLevelCases(void) const
    {
        const int numSubCasesPerEdge       = 4;
        const int numEdges                 = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 :
                                             m_primitiveType == TESSPRIMITIVETYPE_QUADS     ? 4 :
                                                                                              -1;
        const vector<LevelCase> baseResult = PrimitiveSetInvarianceCase::genTessLevelCases();
        vector<LevelCase> result;
        de::Random rnd(123);

        // Generate variants with different values for irrelevant levels.
        for (int baseNdx = 0; baseNdx < (int)baseResult.size(); baseNdx++)
        {
            const TessLevels &base = baseResult[baseNdx].levels[0];
            if (base.inner[0] == 1.0f || (m_primitiveType == TESSPRIMITIVETYPE_QUADS && base.inner[1] == 1.0f))
                continue;

            for (int edgeNdx = 0; edgeNdx < numEdges; edgeNdx++)
            {
                TessLevels levels = base;
                LevelCase levelCase;
                levelCase.mem = edgeNdx;

                for (int subCaseNdx = 0; subCaseNdx < numSubCasesPerEdge; subCaseNdx++)
                {
                    levelCase.levels.push_back(levels);

                    for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++)
                    {
                        if (i != edgeNdx)
                            levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
                    }

                    if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
                        levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
                }

                result.push_back(levelCase);
            }
        }

        return result;
    }

    class IsTriangleTriangleOnOuterEdge
    {
    public:
        IsTriangleTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx)
        {
        }
        bool operator()(const Vec3 *vertices) const
        {
            bool touchesAppropriateEdge = false;
            for (int v = 0; v < 3; v++)
                if (vertices[v][m_edgeNdx] == 0.0f)
                    touchesAppropriateEdge = true;

            if (touchesAppropriateEdge)
            {
                const Vec3 avg = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
                return avg[m_edgeNdx] < avg[(m_edgeNdx + 1) % 3] && avg[m_edgeNdx] < avg[(m_edgeNdx + 2) % 3];
            }
            return false;
        }

    private:
        int m_edgeNdx;
    };

    class IsQuadTriangleOnOuterEdge
    {
    public:
        IsQuadTriangleOnOuterEdge(int edgeNdx) : m_edgeNdx(edgeNdx)
        {
        }

        bool onEdge(const Vec3 &v) const
        {
            return v[m_edgeNdx % 2] == (m_edgeNdx <= 1 ? 0.0f : 1.0f);
        }

        static inline bool onAnyEdge(const Vec3 &v)
        {
            return v[0] == 0.0f || v[0] == 1.0f || v[1] == 0.0f || v[1] == 1.0f;
        }

        bool operator()(const Vec3 *vertices) const
        {
            for (int v = 0; v < 3; v++)
            {
                const Vec3 &a = vertices[v];
                const Vec3 &b = vertices[(v + 1) % 3];
                const Vec3 &c = vertices[(v + 2) % 3];
                if (onEdge(a) && onEdge(b))
                    return true;
                if (onEdge(c) && !onAnyEdge(a) && !onAnyEdge(b) && a[m_edgeNdx % 2] == b[m_edgeNdx % 2])
                    return true;
            }

            return false;
        }

    private:
        int m_edgeNdx;
    };

    virtual bool compare(const vector<Vec3> &coordsA, const vector<Vec3> &coordsB, int outerEdgeNdx) const
    {
        if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
        {
            return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(),
                                       IsTriangleTriangleOnOuterEdge(outerEdgeNdx),
                                       ("inner triangles, and outer triangles corresponding to other edge than edge " +
                                        outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description())
                                           .c_str());
        }
        else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
        {
            return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsQuadTriangleOnOuterEdge(outerEdgeNdx),
                                       ("inner triangles, and outer triangles corresponding to other edge than edge " +
                                        outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description())
                                           .c_str());
        }
        else
            DE_ASSERT(false);

        return true;
    }
};

/*--------------------------------------------------------------------*//*!
 * \brief Base class for testing individual components of tess coords
 *
 * Useful for testing parts of invariance rule #8.
 *//*--------------------------------------------------------------------*/
class TessCoordComponentInvarianceCase : public TestCase
{
public:
    TessCoordComponentInvarianceCase(Context &context, const char *name, const char *description,
                                     TessPrimitiveType primType, SpacingMode spacing, Winding winding,
                                     bool usePointMode)
        : TestCase(context, name, description)
        , m_primitiveType(primType)
        , m_spacing(spacing)
        , m_winding(winding)
        , m_usePointMode(usePointMode)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

protected:
    virtual string tessEvalOutputComponentStatements(const char *tessCoordComponentName,
                                                     const char *outputComponentName) const = 0;
    virtual bool checkTessCoordComponent(float component) const                             = 0;

private:
    static vector<float> genTessLevelCases(int numCases);

    static const int RENDER_SIZE = 16;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;
    const Winding m_winding;
    const bool m_usePointMode;

    SharedPtr<const glu::ShaderProgram> m_program;
};

void TessCoordComponentInvarianceCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp float in_v_attr;\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = 1) out;\n"
                                            "\n"
                                            "in highp float in_tc_attr[];\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
                                            "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
                                            "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
                                            "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
                                            "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
        "\n"
        "out highp vec4 in_f_color;\n"
        "out highp vec3 out_te_output;\n"
        "\n"
        "void main (void)\n"
        "{\n" +
        tessEvalOutputComponentStatements("gl_TessCoord.x", "out_te_output.x") +
        tessEvalOutputComponentStatements("gl_TessCoord.y", "out_te_output.y")

        + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
               tessEvalOutputComponentStatements("gl_TessCoord.z", "out_te_output.z") :
               "    out_te_output.z = 0.0f;\n") +
        "    gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
        "    in_f_color = vec4(1.0);\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in highp vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                              << glu::TransformFeedbackVarying("out_te_output")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void TessCoordComponentInvarianceCase::deinit(void)
{
    m_program.clear();
}

vector<float> TessCoordComponentInvarianceCase::genTessLevelCases(int numCases)
{
    de::Random rnd(123);
    vector<float> result;

    for (int i = 0; i < numCases; i++)
        for (int j = 0; j < 6; j++)
            result.push_back(rnd.getFloat(1.0f, 63.0f));

    return result;
}

TessCoordComponentInvarianceCase::IterateResult TessCoordComponentInvarianceCase::iterate(void)
{
    typedef TransformFeedbackHandler<Vec3> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl       = renderCtx.getFunctions();
    const int numTessLevelCases    = 32;
    const vector<float> tessLevels = genTessLevelCases(numTessLevelCases);
    const uint32_t programGL       = m_program->getProgram();

    gl.useProgram(programGL);
    setViewport(gl, viewport);
    gl.patchParameteri(GL_PATCH_VERTICES, 6);

    {
        // Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
        int maxNumVerticesInDrawCall = 0;
        for (int i = 0; i < numTessLevelCases; i++)
            maxNumVerticesInDrawCall =
                de::max(maxNumVerticesInDrawCall, referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode,
                                                                       &tessLevels[6 * i + 0], &tessLevels[6 * i + 2]));

        {
            const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

            for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < numTessLevelCases; tessLevelCaseNdx++)
            {
                log << TestLog::Message << "Testing with tessellation levels: "
                    << tessellationLevelsString(&tessLevels[6 * tessLevelCaseNdx + 0],
                                                &tessLevels[6 * tessLevelCaseNdx + 2])
                    << TestLog::EndMessage;

                const glu::VertexArrayBinding bindings[] = {
                    glu::va::Float("in_v_attr", 1, (int)6, 0, &tessLevels[6 * tessLevelCaseNdx])};
                const TFHandler::Result tfResult =
                    tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
                                                     DE_LENGTH_OF_ARRAY(bindings), &bindings[0], 6);

                for (int vtxNdx = 0; vtxNdx < (int)tfResult.varying.size(); vtxNdx++)
                {
                    const Vec3 &vec    = tfResult.varying[vtxNdx];
                    const int numComps = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2;

                    for (int compNdx = 0; compNdx < numComps; compNdx++)
                    {
                        if (!checkTessCoordComponent(vec[compNdx]))
                        {
                            log << TestLog::Message << "Note: output value at index " << vtxNdx << " is "
                                << (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? de::toString(vec) :
                                                                                     de::toString(vec.swizzle(0, 1)))
                                << TestLog::EndMessage;
                            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid tessellation coordinate component");
                            return STOP;
                        }
                    }
                }
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test first part of invariance rule #8
 *
 * Test that all (relevant) components of tess coord are in [0,1].
 *//*--------------------------------------------------------------------*/
class TessCoordComponentRangeCase : public TessCoordComponentInvarianceCase
{
public:
    TessCoordComponentRangeCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                                SpacingMode spacing, Winding winding, bool usePointMode)
        : TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode)
    {
    }

protected:
    virtual string tessEvalOutputComponentStatements(const char *tessCoordComponentName,
                                                     const char *outputComponentName) const
    {
        return string() + "\t" + outputComponentName + " = " + tessCoordComponentName + ";\n";
    }

    virtual bool checkTessCoordComponent(float component) const
    {
        if (!de::inRange(component, 0.0f, 1.0f))
        {
            m_testCtx.getLog() << TestLog::Message << "Failure: tess coord component isn't in range [0,1]"
                               << TestLog::EndMessage;
            return false;
        }
        return true;
    }
};

/*--------------------------------------------------------------------*//*!
 * \brief Test second part of invariance rule #8
 *
 * Test that all (relevant) components of tess coord are in [0,1] and
 * 1.0-c is exact for every such component c.
 *//*--------------------------------------------------------------------*/
class OneMinusTessCoordComponentCase : public TessCoordComponentInvarianceCase
{
public:
    OneMinusTessCoordComponentCase(Context &context, const char *name, const char *description,
                                   TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
        : TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode)
    {
    }

protected:
    virtual string tessEvalOutputComponentStatements(const char *tessCoordComponentName,
                                                     const char *outputComponentName) const
    {
        return string() +
               "    {\n"
               "        float oneMinusComp = 1.0 - " +
               tessCoordComponentName +
               ";\n"
               "        " +
               outputComponentName + " = " + tessCoordComponentName +
               " + oneMinusComp;\n"
               "    }\n";
    }

    virtual bool checkTessCoordComponent(float component) const
    {
        if (component != 1.0f)
        {
            m_testCtx.getLog()
                << TestLog::Message
                << "Failure: comp + (1.0-comp) doesn't equal 1.0 for some component of tessellation coordinate"
                << TestLog::EndMessage;
            return false;
        }
        return true;
    }
};

/*--------------------------------------------------------------------*//*!
 * \brief Test that patch is discarded if relevant outer level <= 0.0
 *
 * Draws patches with different combinations of tessellation levels,
 * varying which levels are negative. Verifies by checking that colored
 * pixels exist inside the area of valid primitives, and only black pixels
 * exist inside the area of discarded primitives. An additional sanity
 * test is done, checking that the number of primitives written by TF is
 * correct.
 *//*--------------------------------------------------------------------*/
class PrimitiveDiscardCase : public TestCase
{
public:
    PrimitiveDiscardCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                         SpacingMode spacing, Winding winding, bool usePointMode)
        : TestCase(context, name, description)
        , m_primitiveType(primType)
        , m_spacing(spacing)
        , m_winding(winding)
        , m_usePointMode(usePointMode)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    static vector<float> genAttributes(void);

    static const int RENDER_SIZE = 256;

    const TessPrimitiveType m_primitiveType;
    const SpacingMode m_spacing;
    const Winding m_winding;
    const bool m_usePointMode;

    SharedPtr<const glu::ShaderProgram> m_program;
};

void PrimitiveDiscardCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp float in_v_attr;\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = 1) out;\n"
                                            "\n"
                                            "in highp float in_tc_attr[];\n"
                                            "\n"
                                            "patch out highp vec2 in_te_positionScale;\n"
                                            "patch out highp vec2 in_te_positionOffset;\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
                                            "    in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
                                            "\n"
                                            "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
                                            "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
                                            "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
                                            "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
                                            "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
        "\n"
        "patch in highp vec2 in_te_positionScale;\n"
        "patch in highp vec2 in_te_positionOffset;\n"
        "\n"
        "out highp vec3 out_te_tessCoord;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    out_te_tessCoord = gl_TessCoord;\n"
        "    gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = vec4(1.0);\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                              << glu::TransformFeedbackVarying("out_te_tessCoord")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void PrimitiveDiscardCase::deinit(void)
{
    m_program.clear();
}

vector<float> PrimitiveDiscardCase::genAttributes(void)
{
    // Generate input attributes (tessellation levels, and position scale and
    // offset) for a number of primitives. Each primitive has a different
    // combination of tessellatio levels; each level is either a valid
    // value or an "invalid" value (negative or zero, chosen from
    // invalidTessLevelChoices).

    // \note The attributes are generated in such an order that all of the
    //         valid attribute tuples come before the first invalid one both
    //         in the result vector, and when scanning the resulting 2d grid
    //         of primitives is scanned in y-major order. This makes
    //         verification somewhat simpler.

    static const float baseTessLevels[6]         = {3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
    static const float invalidTessLevelChoices[] = {-0.42f, 0.0f};
    const int numChoices                         = 1 + DE_LENGTH_OF_ARRAY(invalidTessLevelChoices);
    float choices[6][numChoices];
    vector<float> result;

    for (int levelNdx = 0; levelNdx < 6; levelNdx++)
        for (int choiceNdx = 0; choiceNdx < numChoices; choiceNdx++)
            choices[levelNdx][choiceNdx] =
                choiceNdx == 0 ? baseTessLevels[levelNdx] : invalidTessLevelChoices[choiceNdx - 1];

    {
        const int numCols = intPow(numChoices, 6 / 2); // sqrt(numChoices**6) == sqrt(number of primitives)
        const int numRows = numCols;
        int index         = 0;
        int i[6];
        // We could do this with some generic combination-generation function, but meh, it's not that bad.
        for (i[2] = 0; i[2] < numChoices; i[2]++)                     // First  outer
            for (i[3] = 0; i[3] < numChoices; i[3]++)                 // Second outer
                for (i[4] = 0; i[4] < numChoices; i[4]++)             // Third  outer
                    for (i[5] = 0; i[5] < numChoices; i[5]++)         // Fourth outer
                        for (i[0] = 0; i[0] < numChoices; i[0]++)     // First  inner
                            for (i[1] = 0; i[1] < numChoices; i[1]++) // Second inner
                            {
                                for (int j = 0; j < 6; j++)
                                    result.push_back(choices[j][i[j]]);

                                {
                                    const int col = index % numCols;
                                    const int row = index / numCols;
                                    // Position scale.
                                    result.push_back((float)2.0f / (float)numCols);
                                    result.push_back((float)2.0f / (float)numRows);
                                    // Position offset.
                                    result.push_back((float)col / (float)numCols * 2.0f - 1.0f);
                                    result.push_back((float)row / (float)numRows * 2.0f - 1.0f);
                                }

                                index++;
                            }
    }

    return result;
}

PrimitiveDiscardCase::IterateResult PrimitiveDiscardCase::iterate(void)
{
    typedef TransformFeedbackHandler<Vec3> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl         = renderCtx.getFunctions();
    const vector<float> attributes   = genAttributes();
    const int numAttribsPerPrimitive = 6 + 2 + 2; // Tess levels, scale, offset.
    const int numPrimitives          = (int)attributes.size() / numAttribsPerPrimitive;
    const uint32_t programGL         = m_program->getProgram();

    gl.useProgram(programGL);
    setViewport(gl, viewport);
    gl.patchParameteri(GL_PATCH_VERTICES, numAttribsPerPrimitive);

    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);

    // Check the convenience assertion that all discarded patches come after the last non-discarded patch.
    {
        bool discardedPatchEncountered = false;
        for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
        {
            const bool discard = isPatchDiscarded(m_primitiveType, &attributes[numAttribsPerPrimitive * patchNdx + 2]);
            DE_ASSERT(discard || !discardedPatchEncountered);
            discardedPatchEncountered = discard;
        }
        DE_UNREF(discardedPatchEncountered);
    }

    {
        int numVerticesInDrawCall = 0;
        for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
            numVerticesInDrawCall += referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode,
                                                          &attributes[numAttribsPerPrimitive * patchNdx + 0],
                                                          &attributes[numAttribsPerPrimitive * patchNdx + 2]);

        log << TestLog::Message << "Note: rendering " << numPrimitives
            << " patches; first patches have valid relevant outer levels, "
            << "but later patches have one or more invalid (i.e. less than or equal to 0.0) relevant outer levels"
            << TestLog::EndMessage;

        {
            const TFHandler tfHandler(m_context.getRenderContext(), numVerticesInDrawCall);
            const glu::VertexArrayBinding bindings[] = {
                glu::va::Float("in_v_attr", 1, (int)attributes.size(), 0, &attributes[0])};
            const TFHandler::Result tfResult =
                tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
                                                 DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)attributes.size());
            const tcu::Surface pixels = getPixels(renderCtx, viewport);

            log << TestLog::Image("RenderedImage", "Rendered image", pixels);

            if ((int)tfResult.varying.size() != numVerticesInDrawCall)
            {
                log << TestLog::Message << "Failure: expected " << numVerticesInDrawCall
                    << " vertices from transform feedback, got " << tfResult.varying.size() << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrong number of tessellation coordinates");
                return STOP;
            }

            // Check that white pixels are found around every non-discarded
            // patch, and that only black pixels are found after the last
            // non-discarded patch.
            {
                int lastWhitePixelRow                               = 0;
                int secondToLastWhitePixelRow                       = 0;
                int lastWhitePixelColumnOnSecondToLastWhitePixelRow = 0;

                for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
                {
                    const float *const attr = &attributes[numAttribsPerPrimitive * patchNdx];
                    const bool validLevels  = !isPatchDiscarded(m_primitiveType, &attr[2]);

                    if (validLevels)
                    {
                        // Not a discarded patch; check that at least one white pixel is found in its area.

                        const float *const scale  = &attr[6];
                        const float *const offset = &attr[8];
                        const int x0              = (int)((offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) - 1;
                        const int x1      = (int)((scale[0] + offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) + 1;
                        const int y0      = (int)((offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) - 1;
                        const int y1      = (int)((scale[1] + offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) + 1;
                        const bool isMSAA = renderCtx.getRenderTarget().getNumSamples() > 1;
                        bool pixelOk      = false;

                        if (y1 > lastWhitePixelRow)
                        {
                            secondToLastWhitePixelRow = lastWhitePixelRow;
                            lastWhitePixelRow         = y1;
                        }
                        lastWhitePixelColumnOnSecondToLastWhitePixelRow = x1;

                        for (int y = y0; y <= y1 && !pixelOk; y++)
                            for (int x = x0; x <= x1 && !pixelOk; x++)
                            {
                                if (!de::inBounds(x, 0, pixels.getWidth()) || !de::inBounds(y, 0, pixels.getHeight()))
                                    continue;

                                if (isMSAA)
                                {
                                    if (pixels.getPixel(x, y) != tcu::RGBA::black())
                                        pixelOk = true;
                                }
                                else
                                {
                                    if (pixels.getPixel(x, y) == tcu::RGBA::white())
                                        pixelOk = true;
                                }
                            }

                        if (!pixelOk)
                        {
                            log << TestLog::Message << "Failure: expected at least one "
                                << (isMSAA ? "non-black" : "white") << " pixel in the rectangle "
                                << "[x0=" << x0 << ", y0=" << y0 << ", x1=" << x1 << ", y1=" << y1 << "]"
                                << TestLog::EndMessage << TestLog::Message
                                << "Note: the rectangle approximately corresponds to the patch with these tessellation "
                                   "levels: "
                                << tessellationLevelsString(&attr[0], &attr[1]) << TestLog::EndMessage;
                            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
                            return STOP;
                        }
                    }
                    else
                    {
                        // First discarded primitive patch; the remaining are guaranteed to be discarded ones as well.

                        for (int y = 0; y < pixels.getHeight(); y++)
                            for (int x = 0; x < pixels.getWidth(); x++)
                            {
                                if (y > lastWhitePixelRow || (y > secondToLastWhitePixelRow &&
                                                              x > lastWhitePixelColumnOnSecondToLastWhitePixelRow))
                                {
                                    if (pixels.getPixel(x, y) != tcu::RGBA::black())
                                    {
                                        log << TestLog::Message
                                            << "Failure: expected all pixels to be black in the area "
                                            << (lastWhitePixelColumnOnSecondToLastWhitePixelRow <
                                                        pixels.getWidth() - 1 ?
                                                    string() + "y > " + de::toString(lastWhitePixelRow) + " || (y > " +
                                                        de::toString(secondToLastWhitePixelRow) + " && x > " +
                                                        de::toString(lastWhitePixelColumnOnSecondToLastWhitePixelRow) +
                                                        ")" :
                                                    string() + "y > " + de::toString(lastWhitePixelRow))
                                            << " (they all correspond to patches that should be discarded)"
                                            << TestLog::EndMessage << TestLog::Message << "Note: pixel "
                                            << tcu::IVec2(x, y) << " isn't black" << TestLog::EndMessage;
                                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
                                        return STOP;
                                    }
                                }
                            }

                        break;
                    }
                }
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Case testing user-defined IO between TCS and TES
 *
 * TCS outputs various values to TES, including aggregates. The outputs
 * can be per-patch or per-vertex, and if per-vertex, they can also be in
 * an IO block. Per-vertex input array size can be left implicit (i.e.
 * inputArray[]) or explicit either by gl_MaxPatchVertices or an integer
 * literal whose value is queried from GL.
 *
 * The values output are generated in TCS and verified in TES against
 * similarly generated values. In case a verification of a value fails, the
 * index of the invalid value is output with TF.
 * As a quick check, also the rendering result is verified (against pre-
 * rendered reference).
 *//*--------------------------------------------------------------------*/
class UserDefinedIOCase : public TestCase
{
public:
    enum IOType
    {
        IO_TYPE_PER_PATCH = 0,
        IO_TYPE_PER_PATCH_ARRAY,
        IO_TYPE_PER_PATCH_BLOCK,
        IO_TYPE_PER_PATCH_BLOCK_ARRAY,
        IO_TYPE_PER_VERTEX,
        IO_TYPE_PER_VERTEX_BLOCK,

        IO_TYPE_LAST
    };

    enum VertexIOArraySize
    {
        VERTEX_IO_ARRAY_SIZE_IMPLICIT = 0,
        VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN, //!< Use gl_MaxPatchVertices as size for per-vertex input array.
        VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY, //!< Query GL_MAX_PATCH_VERTICES, and use that as size for per-vertex input array.

        VERTEX_IO_ARRAY_SIZE_LAST
    };

    enum TessControlOutArraySize
    {
        TESS_CONTROL_OUT_ARRAY_SIZE_IMPLICIT = 0,
        TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT,
        TESS_CONTROL_OUT_ARRAY_SIZE_QUERY,
        TESS_CONTROL_OUT_ARRAY_SIZE_SHADER_BUILTIN
    };

    UserDefinedIOCase(Context &context, const char *name, const char *description, TessPrimitiveType primType,
                      IOType ioType, VertexIOArraySize vertexIOArraySize,
                      TessControlOutArraySize tessControlOutArraySize, const char *referenceImagePath)
        : TestCase(context, name, description)
        , m_primitiveType(primType)
        , m_ioType(ioType)
        , m_vertexIOArraySize(vertexIOArraySize)
        , m_tessControlOutArraySize(tessControlOutArraySize)
        , m_referenceImagePath(referenceImagePath)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    typedef string (*BasicTypeVisitFunc)(const string &name, glu::DataType type,
                                         int indentationDepth); //!< See glslTraverseBasicTypes below.

    class TopLevelObject
    {
    public:
        virtual ~TopLevelObject(void)
        {
        }

        virtual string name(void) const                                = 0;
        virtual string declare(void) const                             = 0;
        virtual string declareArray(const string &arraySizeExpr) const = 0;
        virtual string glslTraverseBasicTypeArray(
            int numArrayElements, //!< If negative, traverse just array[gl_InvocationID], not all indices.
            int indentationDepth, BasicTypeVisitFunc) const                                  = 0;
        virtual string glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc) const = 0;
        virtual int numBasicSubobjectsInElementType(void) const                              = 0;
        virtual string basicSubobjectAtIndex(int index, int arraySize) const                 = 0;
    };

    class Variable : public TopLevelObject
    {
    public:
        Variable(const string &name_, const glu::VarType &type, bool isArray)
            : m_name(name_)
            , m_type(type)
            , m_isArray(isArray)
        {
            DE_ASSERT(!type.isArrayType());
        }

        string name(void) const
        {
            return m_name;
        }
        string declare(void) const;
        string declareArray(const string &arraySizeExpr) const;
        string glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const;
        string glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc) const;
        int numBasicSubobjectsInElementType(void) const;
        string basicSubobjectAtIndex(int index, int arraySize) const;

    private:
        string m_name;
        glu::VarType
            m_type; //!< If this Variable is an array element, m_type is the element type; otherwise just the variable type.
        const bool m_isArray;
    };

    class IOBlock : public TopLevelObject
    {
    public:
        struct Member
        {
            string name;
            glu::VarType type;
            Member(const string &n, const glu::VarType &t) : name(n), type(t)
            {
            }
        };

        IOBlock(const string &blockName, const string &interfaceName, const vector<Member> &members)
            : m_blockName(blockName)
            , m_interfaceName(interfaceName)
            , m_members(members)
        {
        }

        string name(void) const
        {
            return m_interfaceName;
        }
        string declare(void) const;
        string declareArray(const string &arraySizeExpr) const;
        string glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const;
        string glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc) const;
        int numBasicSubobjectsInElementType(void) const;
        string basicSubobjectAtIndex(int index, int arraySize) const;

    private:
        string m_blockName;
        string m_interfaceName;
        vector<Member> m_members;
    };

    static string glslTraverseBasicTypes(const string &rootName, const glu::VarType &rootType, int arrayNestingDepth,
                                         int indentationDepth, BasicTypeVisitFunc visit);

    static string glslAssignBasicTypeObject(const string &name, glu::DataType, int indentationDepth);
    static string glslCheckBasicTypeObject(const string &name, glu::DataType, int indentationDepth);
    static int numBasicSubobjectsInElementType(const vector<SharedPtr<TopLevelObject>> &);
    static string basicSubobjectAtIndex(int index, const vector<SharedPtr<TopLevelObject>> &, int topLevelArraySizes);

    enum
    {
        RENDER_SIZE = 256
    };
    enum
    {
        NUM_OUTPUT_VERTICES = 5
    };
    enum
    {
        NUM_PER_PATCH_ARRAY_ELEMS = 3
    };
    enum
    {
        NUM_PER_PATCH_BLOCKS = 2
    };

    const TessPrimitiveType m_primitiveType;
    const IOType m_ioType;
    const VertexIOArraySize m_vertexIOArraySize;
    const TessControlOutArraySize m_tessControlOutArraySize;
    const string m_referenceImagePath;

    vector<glu::StructType> m_structTypes;
    vector<SharedPtr<TopLevelObject>> m_tcsOutputs;
    vector<SharedPtr<TopLevelObject>> m_tesInputs;

    SharedPtr<const glu::ShaderProgram> m_program;
};

/*--------------------------------------------------------------------*//*!
 * \brief Generate GLSL code to traverse (possibly aggregate) object
 *
 * Generates a string that represents GLSL code that traverses the
 * basic-type subobjects in a rootType-typed object named rootName. Arrays
 * are traversed with loops and struct members are each traversed
 * separately. The code for each basic-type subobject is generated with
 * the function given as the 'visit' argument.
 *//*--------------------------------------------------------------------*/
string UserDefinedIOCase::glslTraverseBasicTypes(const string &rootName, const glu::VarType &rootType,
                                                 int arrayNestingDepth, int indentationDepth, BasicTypeVisitFunc visit)
{
    if (rootType.isBasicType())
        return visit(rootName, rootType.getBasicType(), indentationDepth);
    else if (rootType.isArrayType())
    {
        const string indentation   = string(indentationDepth, '\t');
        const string loopIndexName = "i" + de::toString(arrayNestingDepth);
        const string arrayLength   = de::toString(rootType.getArraySize());
        return indentation + "for (int " + loopIndexName + " = 0; " + loopIndexName + " < " +
               de::toString(rootType.getArraySize()) + "; " + loopIndexName + "++)\n" + indentation + "{\n" +
               glslTraverseBasicTypes(rootName + "[" + loopIndexName + "]", rootType.getElementType(),
                                      arrayNestingDepth + 1, indentationDepth + 1, visit) +
               indentation + "}\n";
    }
    else if (rootType.isStructType())
    {
        const glu::StructType &structType = *rootType.getStructPtr();
        const int numMembers              = structType.getNumMembers();
        string result;

        for (int membNdx = 0; membNdx < numMembers; membNdx++)
        {
            const glu::StructMember &member = structType.getMember(membNdx);
            result += glslTraverseBasicTypes(rootName + "." + member.getName(), member.getType(), arrayNestingDepth,
                                             indentationDepth, visit);
        }

        return result;
    }
    else
    {
        DE_ASSERT(false);
        return "";
    }
}

string UserDefinedIOCase::Variable::declare(void) const
{
    DE_ASSERT(!m_isArray);
    return de::toString(glu::declare(m_type, m_name)) + ";\n";
}

string UserDefinedIOCase::Variable::declareArray(const string &sizeExpr) const
{
    DE_ASSERT(m_isArray);
    return de::toString(glu::declare(m_type, m_name)) + "[" + sizeExpr + "];\n";
}

string UserDefinedIOCase::IOBlock::declare(void) const
{
    std::ostringstream buf;

    buf << m_blockName << "\n"
        << "{\n";

    for (int i = 0; i < (int)m_members.size(); i++)
        buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n";

    buf << "} " << m_interfaceName << ";\n";
    return buf.str();
}

string UserDefinedIOCase::IOBlock::declareArray(const string &sizeExpr) const
{
    std::ostringstream buf;

    buf << m_blockName << "\n"
        << "{\n";

    for (int i = 0; i < (int)m_members.size(); i++)
        buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n";

    buf << "} " << m_interfaceName << "[" << sizeExpr << "];\n";
    return buf.str();
}

string UserDefinedIOCase::Variable::glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth,
                                                               BasicTypeVisitFunc visit) const
{
    DE_ASSERT(m_isArray);

    const bool traverseAsArray = numArrayElements >= 0;
    const string traversedName = m_name + (!traverseAsArray ? "[gl_InvocationID]" : "");
    const glu::VarType type    = traverseAsArray ? glu::VarType(m_type, numArrayElements) : m_type;

    return UserDefinedIOCase::glslTraverseBasicTypes(traversedName, type, 0, indentationDepth, visit);
}

string UserDefinedIOCase::Variable::glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc visit) const
{
    DE_ASSERT(!m_isArray);

    return UserDefinedIOCase::glslTraverseBasicTypes(m_name, m_type, 0, indentationDepth, visit);
}

string UserDefinedIOCase::IOBlock::glslTraverseBasicTypeArray(int numArrayElements, int indentationDepth,
                                                              BasicTypeVisitFunc visit) const
{
    if (numArrayElements >= 0)
    {
        const string indentation = string(indentationDepth, '\t');
        string result =
            indentation + "for (int i0 = 0; i0 < " + de::toString(numArrayElements) + "; i0++)\n" + indentation + "{\n";
        for (int i = 0; i < (int)m_members.size(); i++)
            result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[i0]." + m_members[i].name,
                                                                m_members[i].type, 1, indentationDepth + 1, visit);
        result += indentation + "}\n";
        return result;
    }
    else
    {
        string result;
        for (int i = 0; i < (int)m_members.size(); i++)
            result +=
                UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[gl_InvocationID]." + m_members[i].name,
                                                          m_members[i].type, 0, indentationDepth, visit);
        return result;
    }
}

string UserDefinedIOCase::IOBlock::glslTraverseBasicType(int indentationDepth, BasicTypeVisitFunc visit) const
{
    string result;
    for (int i = 0; i < (int)m_members.size(); i++)
        result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "." + m_members[i].name,
                                                            m_members[i].type, 0, indentationDepth, visit);
    return result;
}

int UserDefinedIOCase::Variable::numBasicSubobjectsInElementType(void) const
{
    return numBasicSubobjects(m_type);
}

int UserDefinedIOCase::IOBlock::numBasicSubobjectsInElementType(void) const
{
    int result = 0;
    for (int i = 0; i < (int)m_members.size(); i++)
        result += numBasicSubobjects(m_members[i].type);
    return result;
}

string UserDefinedIOCase::Variable::basicSubobjectAtIndex(int subobjectIndex, int arraySize) const
{
    const glu::VarType type = m_isArray ? glu::VarType(m_type, arraySize) : m_type;
    int currentIndex        = 0;

    for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&type);
         basicIt != glu::BasicTypeIterator::end(&type); ++basicIt)
    {
        if (currentIndex == subobjectIndex)
            return m_name + de::toString(glu::TypeAccessFormat(type, basicIt.getPath()));
        currentIndex++;
    }
    DE_ASSERT(false);
    return "";
}

string UserDefinedIOCase::IOBlock::basicSubobjectAtIndex(int subobjectIndex, int arraySize) const
{
    int currentIndex = 0;
    for (int arrayNdx = 0; arrayNdx < arraySize; arrayNdx++)
    {
        for (int memberNdx = 0; memberNdx < (int)m_members.size(); memberNdx++)
        {
            const glu::VarType &membType = m_members[memberNdx].type;
            for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&membType);
                 basicIt != glu::BasicTypeIterator::end(&membType); ++basicIt)
            {
                if (currentIndex == subobjectIndex)
                    return m_interfaceName + "[" + de::toString(arrayNdx) + "]." + m_members[memberNdx].name +
                           de::toString(glu::TypeAccessFormat(membType, basicIt.getPath()));
                currentIndex++;
            }
        }
    }
    DE_ASSERT(false);
    return "";
}

// Used as the 'visit' argument for glslTraverseBasicTypes.
string UserDefinedIOCase::glslAssignBasicTypeObject(const string &name, glu::DataType type, int indentationDepth)
{
    const int scalarSize     = glu::getDataTypeScalarSize(type);
    const string indentation = string(indentationDepth, '\t');
    string result;

    result += indentation + name + " = ";

    if (type != glu::TYPE_FLOAT)
        result += string() + glu::getDataTypeName(type) + "(";
    for (int i = 0; i < scalarSize; i++)
        result += (i > 0 ? ", v+" + de::floatToString(0.8f * (float)i, 1) : "v");
    if (type != glu::TYPE_FLOAT)
        result += ")";
    result += ";\n" + indentation + "v += 0.4;\n";
    return result;
}

// Used as the 'visit' argument for glslTraverseBasicTypes.
string UserDefinedIOCase::glslCheckBasicTypeObject(const string &name, glu::DataType type, int indentationDepth)
{
    const int scalarSize     = glu::getDataTypeScalarSize(type);
    const string indentation = string(indentationDepth, '\t');
    string result;

    result += indentation + "allOk = allOk && compare_" + glu::getDataTypeName(type) + "(" + name + ", ";

    if (type != glu::TYPE_FLOAT)
        result += string() + glu::getDataTypeName(type) + "(";
    for (int i = 0; i < scalarSize; i++)
        result += (i > 0 ? ", v+" + de::floatToString(0.8f * (float)i, 1) : "v");
    if (type != glu::TYPE_FLOAT)
        result += ")";
    result += ");\n" + indentation + "v += 0.4;\n" + indentation + "if (allOk) firstFailedInputIndex++;\n";

    return result;
}

int UserDefinedIOCase::numBasicSubobjectsInElementType(const vector<SharedPtr<TopLevelObject>> &objects)
{
    int result = 0;
    for (int i = 0; i < (int)objects.size(); i++)
        result += objects[i]->numBasicSubobjectsInElementType();
    return result;
}

string UserDefinedIOCase::basicSubobjectAtIndex(int subobjectIndex, const vector<SharedPtr<TopLevelObject>> &objects,
                                                int topLevelArraySize)
{
    int currentIndex = 0;
    int objectIndex  = 0;
    for (; currentIndex < subobjectIndex; objectIndex++)
        currentIndex += objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize;
    if (currentIndex > subobjectIndex)
    {
        objectIndex--;
        currentIndex -= objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize;
    }

    return objects[objectIndex]->basicSubobjectAtIndex(subobjectIndex - currentIndex, topLevelArraySize);
}

void UserDefinedIOCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    const bool isPerPatchIO = m_ioType == IO_TYPE_PER_PATCH || m_ioType == IO_TYPE_PER_PATCH_ARRAY ||
                              m_ioType == IO_TYPE_PER_PATCH_BLOCK || m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY;

    const bool isExplicitVertexArraySize = m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ||
                                           m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY;

    const string vertexAttrArrayInputSize = m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_IMPLICIT ?
                                                "" :
                                            m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ?
                                                "gl_MaxPatchVertices" :
                                            m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY ?
                                                de::toString(m_context.getContextInfo().getInt(GL_MAX_PATCH_VERTICES)) :
                                                deFatalStr("Invalid VertexIOArraySize");

    const char *const maybePatch = isPerPatchIO ? "patch " : "";
    const string outMaybePatch   = string() + maybePatch + "out ";
    const string inMaybePatch    = string() + maybePatch + "in ";
    const bool useBlock          = m_ioType == IO_TYPE_PER_VERTEX_BLOCK || m_ioType == IO_TYPE_PER_PATCH_BLOCK ||
                          m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY;

    string tcsDeclarations;
    string tcsStatements;

    string tesDeclarations;
    string tesStatements;

    {
        m_structTypes.push_back(glu::StructType("S"));

        const glu::VarType highpFloat(glu::TYPE_FLOAT, glu::PRECISION_HIGHP);
        glu::StructType &structType = m_structTypes.back();
        const glu::VarType structVarType(&structType);
        bool usedStruct = false;

        structType.addMember("x", glu::VarType(glu::TYPE_INT, glu::PRECISION_HIGHP));
        structType.addMember("y", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));

        if (useBlock)
        {
            // It is illegal to have a structure containing an array as an output variable
            structType.addMember("z", glu::VarType(highpFloat, 2));
        }

        if (useBlock)
        {
            const bool useLightweightBlock =
                (m_ioType ==
                 IO_TYPE_PER_PATCH_BLOCK_ARRAY); // use leaner block to make sure it is not larger than allowed (per-patch storage is very limited)
            vector<IOBlock::Member> blockMembers;

            if (!useLightweightBlock)
                blockMembers.push_back(IOBlock::Member("blockS", structVarType));

            blockMembers.push_back(IOBlock::Member("blockFa", glu::VarType(highpFloat, 3)));
            blockMembers.push_back(IOBlock::Member("blockSa", glu::VarType(structVarType, 2)));
            blockMembers.push_back(IOBlock::Member("blockF", highpFloat));

            m_tcsOutputs.push_back(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "tcBlock", blockMembers)));
            m_tesInputs.push_back(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "teBlock", blockMembers)));

            usedStruct = true;
        }
        else
        {
            const Variable var0("in_te_s", structVarType, m_ioType != IO_TYPE_PER_PATCH);
            const Variable var1("in_te_f", highpFloat, m_ioType != IO_TYPE_PER_PATCH);

            if (m_ioType != IO_TYPE_PER_PATCH_ARRAY)
            {
                // Arrays of structures are disallowed, add struct cases only if not arrayed variable
                m_tcsOutputs.push_back(SharedPtr<TopLevelObject>(new Variable(var0)));
                m_tesInputs.push_back(SharedPtr<TopLevelObject>(new Variable(var0)));

                usedStruct = true;
            }

            m_tcsOutputs.push_back(SharedPtr<TopLevelObject>(new Variable(var1)));
            m_tesInputs.push_back(SharedPtr<TopLevelObject>(new Variable(var1)));
        }

        tcsDeclarations += "in " + Variable("in_tc_attr", highpFloat, true).declareArray(vertexAttrArrayInputSize);

        if (usedStruct)
            tcsDeclarations += de::toString(glu::declare(structType)) + ";\n";

        tcsStatements += "\t{\n"
                         "\t\thighp float v = 1.3;\n";

        for (int tcsOutputNdx = 0; tcsOutputNdx < (int)m_tcsOutputs.size(); tcsOutputNdx++)
        {
            const TopLevelObject &output = *m_tcsOutputs[tcsOutputNdx];
            const int numElements        = !isPerPatchIO ? -1 //!< \note -1 means indexing with gl_InstanceID
                                           :
                                           m_ioType == IO_TYPE_PER_PATCH             ? 1 :
                                           m_ioType == IO_TYPE_PER_PATCH_ARRAY       ? NUM_PER_PATCH_ARRAY_ELEMS :
                                           m_ioType == IO_TYPE_PER_PATCH_BLOCK       ? 1 :
                                           m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS :
                                                                                       -2;
            const bool isArray           = (numElements != 1);

            DE_ASSERT(numElements != -2);

            if (isArray)
            {
                tcsDeclarations +=
                    outMaybePatch +
                    output.declareArray(m_ioType == IO_TYPE_PER_PATCH_ARRAY ?
                                            de::toString(int(NUM_PER_PATCH_ARRAY_ELEMS)) :
                                        m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ?
                                            de::toString(int(NUM_PER_PATCH_BLOCKS)) :
                                        m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT ?
                                            de::toString(int(NUM_OUTPUT_VERTICES)) :
                                        m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_QUERY ?
                                            de::toString(m_context.getContextInfo().getInt(GL_MAX_PATCH_VERTICES)) :
                                        m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_SHADER_BUILTIN ?
                                            "gl_MaxPatchVertices" :
                                            "");
            }
            else
                tcsDeclarations += outMaybePatch + output.declare();

            if (!isPerPatchIO)
                tcsStatements += "\t\tv += float(gl_InvocationID)*" +
                                 de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) + ";\n";

            tcsStatements += "\n\t\t// Assign values to output " + output.name() + "\n";
            if (isArray)
                tcsStatements += output.glslTraverseBasicTypeArray(numElements, 2, glslAssignBasicTypeObject);
            else
                tcsStatements += output.glslTraverseBasicType(2, glslAssignBasicTypeObject);

            if (!isPerPatchIO)
                tcsStatements += "\t\tv += float(" + de::toString(int(NUM_OUTPUT_VERTICES)) + "-gl_InvocationID-1)*" +
                                 de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) + ";\n";
        }
        tcsStatements += "\t}\n";

        if (usedStruct)
            tesDeclarations += de::toString(glu::declare(structType)) + ";\n";

        tesStatements += "\tbool allOk = true;\n"
                         "\thighp uint firstFailedInputIndex = 0u;\n"
                         "\t{\n"
                         "\t\thighp float v = 1.3;\n";
        for (int tesInputNdx = 0; tesInputNdx < (int)m_tesInputs.size(); tesInputNdx++)
        {
            const TopLevelObject &input = *m_tesInputs[tesInputNdx];
            const int numElements       = !isPerPatchIO                             ? (int)NUM_OUTPUT_VERTICES :
                                          m_ioType == IO_TYPE_PER_PATCH             ? 1 :
                                          m_ioType == IO_TYPE_PER_PATCH_BLOCK       ? 1 :
                                          m_ioType == IO_TYPE_PER_PATCH_ARRAY       ? NUM_PER_PATCH_ARRAY_ELEMS :
                                          m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS :
                                                                                      -2;
            const bool isArray          = (numElements != 1);

            DE_ASSERT(numElements != -2);

            if (isArray)
                tesDeclarations += inMaybePatch + input.declareArray(m_ioType == IO_TYPE_PER_PATCH_ARRAY ?
                                                                         de::toString(int(NUM_PER_PATCH_ARRAY_ELEMS)) :
                                                                     m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ?
                                                                         de::toString(int(NUM_PER_PATCH_BLOCKS)) :
                                                                     isExplicitVertexArraySize ?
                                                                         de::toString(vertexAttrArrayInputSize) :
                                                                         "");
            else
                tesDeclarations += inMaybePatch + input.declare();

            tesStatements += "\n\t\t// Check values in input " + input.name() + "\n";
            if (isArray)
                tesStatements += input.glslTraverseBasicTypeArray(numElements, 2, glslCheckBasicTypeObject);
            else
                tesStatements += input.glslTraverseBasicType(2, glslCheckBasicTypeObject);
        }
        tesStatements += "\t}\n";
    }

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp float in_v_attr;\n"
                                     "out highp float in_tc_attr;\n"
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    in_tc_attr = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate("${GLSL_VERSION_DECL}\n"
                                            "${TESSELLATION_SHADER_REQUIRE}\n"
                                            "${GLSL_PER_VERTEX_IN_ARR}\n"
                                            "${GLSL_PER_VERTEX_OUT_ARR}\n"
                                            "\n"
                                            "layout (vertices = " +
                                            de::toString(int(NUM_OUTPUT_VERTICES)) +
                                            ") out;\n"
                                            "\n" +
                                            tcsDeclarations +
                                            "\n"
                                            "patch out highp vec2 in_te_positionScale;\n"
                                            "patch out highp vec2 in_te_positionOffset;\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n" +
                                            tcsStatements +
                                            "\n"
                                            "    in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
                                            "    in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
                                            "\n"
                                            "    gl_TessLevelInner[0] = in_tc_attr[0];\n"
                                            "    gl_TessLevelInner[1] = in_tc_attr[1];\n"
                                            "\n"
                                            "    gl_TessLevelOuter[0] = in_tc_attr[2];\n"
                                            "    gl_TessLevelOuter[1] = in_tc_attr[3];\n"
                                            "    gl_TessLevelOuter[2] = in_tc_attr[4];\n"
                                            "    gl_TessLevelOuter[3] = in_tc_attr[5];\n"
                                            "}\n");
    std::string tessellationEvaluationTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT}\n"
        "\n" +
        getTessellationEvaluationInLayoutString(m_primitiveType) + "\n" + tesDeclarations +
        "\n"
        "patch in highp vec2 in_te_positionScale;\n"
        "patch in highp vec2 in_te_positionOffset;\n"
        "\n"
        "out highp vec4 in_f_color;\n"
        "// Will contain the index of the first incorrect input,\n"
        "// or the number of inputs if all are correct\n"
        "flat out highp uint out_te_firstFailedInputIndex;\n"
        "\n"
        "bool compare_int   (int   a, int   b) { return a == b; }\n"
        "bool compare_float (float a, float b) { return abs(a - b) < 0.01f; }\n"
        "bool compare_vec4  (vec4  a, vec4  b) { return all(lessThan(abs(a - b), vec4(0.01f))); }\n"
        "\n"
        "void main (void)\n"
        "{\n" +
        tesStatements +
        "\n"
        "    gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n"
        "    in_f_color = allOk ? vec4(0.0, 1.0, 0.0, 1.0)\n"
        "                       : vec4(1.0, 0.0, 0.0, 1.0);\n"
        "    out_te_firstFailedInputIndex = firstFailedInputIndex;\n"
        "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in highp vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))
                              << glu::TransformFeedbackVarying("out_te_firstFailedInputIndex")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void UserDefinedIOCase::deinit(void)
{
    m_program.clear();
}

UserDefinedIOCase::IterateResult UserDefinedIOCase::iterate(void)
{
    typedef TransformFeedbackHandler<uint32_t> TFHandler;

    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl                 = renderCtx.getFunctions();
    static const float attributes[6 + 2 + 2] = {
        /* inner */ 3.0f,      4.0f, /* outer */ 5.0f,        6.0f, 7.0f, 8.0f,
        /* pos. scale */ 1.2f, 1.3f, /* pos. offset */ -0.3f, -0.4f};
    const uint32_t programGL = m_program->getProgram();
    const int numVertices =
        referenceVertexCount(m_primitiveType, SPACINGMODE_EQUAL, false, &attributes[0], &attributes[2]);
    const TFHandler tfHandler(renderCtx, numVertices);
    tcu::ResultCollector result;

    gl.useProgram(programGL);
    setViewport(gl, viewport);
    gl.patchParameteri(GL_PATCH_VERTICES, DE_LENGTH_OF_ARRAY(attributes));

    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);

    {
        const glu::VertexArrayBinding bindings[] = {
            glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attributes), 0, &attributes[0])};
        const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(
            programGL, outputPrimitiveTypeGL(m_primitiveType, false), DE_LENGTH_OF_ARRAY(bindings), &bindings[0],
            DE_LENGTH_OF_ARRAY(attributes));

        {
            const tcu::Surface pixels         = getPixels(renderCtx, viewport);
            const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str());
            const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(),
                                                   pixels.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

            if (!success)
                result.fail("Image comparison failed");
        }

        if ((int)tfResult.varying.size() != numVertices)
        {
            log << TestLog::Message << "Failure: transform feedback returned " << tfResult.varying.size()
                << " vertices; expected " << numVertices << TestLog::EndMessage;
            result.fail("Wrong number of vertices");
        }
        else
        {
            const int topLevelArraySize = (m_ioType == IO_TYPE_PER_PATCH             ? 1 :
                                           m_ioType == IO_TYPE_PER_PATCH_ARRAY       ? NUM_PER_PATCH_ARRAY_ELEMS :
                                           m_ioType == IO_TYPE_PER_PATCH_BLOCK       ? 1 :
                                           m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY ? NUM_PER_PATCH_BLOCKS :
                                                                                       (int)NUM_OUTPUT_VERTICES);
            const int numTEInputs       = numBasicSubobjectsInElementType(m_tesInputs) * topLevelArraySize;

            for (int vertexNdx = 0; vertexNdx < (int)numVertices; vertexNdx++)
            {
                if (tfResult.varying[vertexNdx] > (uint32_t)numTEInputs)
                {
                    log << TestLog::Message << "Failure: out_te_firstFailedInputIndex has value "
                        << tfResult.varying[vertexNdx] << ", should be in range [0, " << numTEInputs << "]"
                        << TestLog::EndMessage;
                    result.fail("Invalid transform feedback output");
                }
                else if (tfResult.varying[vertexNdx] != (uint32_t)numTEInputs)
                {
                    log << TestLog::Message << "Failure: in tessellation evaluation shader, check for input "
                        << basicSubobjectAtIndex(tfResult.varying[vertexNdx], m_tesInputs, topLevelArraySize)
                        << " failed" << TestLog::EndMessage;
                    result.fail("Invalid input value in tessellation evaluation shader");
                }
            }
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Pass gl_Position between VS and TCS, or between TCS and TES.
 *
 * In TCS gl_Position is in the gl_out[] block and in TES in the gl_in[]
 * block, and has no special semantics in those. Arbitrary vec4 data can
 * thus be passed there.
 *//*--------------------------------------------------------------------*/
class GLPositionCase : public TestCase
{
public:
    enum CaseType
    {
        CASETYPE_VS_TO_TCS = 0,
        CASETYPE_TCS_TO_TES,
        CASETYPE_VS_TO_TCS_TO_TES,

        CASETYPE_LAST
    };

    GLPositionCase(Context &context, const char *name, const char *description, CaseType caseType,
                   const char *referenceImagePath)
        : TestCase(context, name, description)
        , m_caseType(caseType)
        , m_referenceImagePath(referenceImagePath)
    {
    }

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

    static const char *getCaseTypeName(CaseType type);

private:
    static const int RENDER_SIZE = 256;

    const CaseType m_caseType;
    const string m_referenceImagePath;

    SharedPtr<const glu::ShaderProgram> m_program;
};

const char *GLPositionCase::getCaseTypeName(CaseType type)
{
    switch (type)
    {
    case CASETYPE_VS_TO_TCS:
        return "gl_position_vs_to_tcs";
    case CASETYPE_TCS_TO_TES:
        return "gl_position_tcs_to_tes";
    case CASETYPE_VS_TO_TCS_TO_TES:
        return "gl_position_vs_to_tcs_to_tes";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

void GLPositionCase::init(void)
{
    checkTessellationSupport(m_context);
    checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

    const bool vsToTCS  = m_caseType == CASETYPE_VS_TO_TCS || m_caseType == CASETYPE_VS_TO_TCS_TO_TES;
    const bool tcsToTES = m_caseType == CASETYPE_TCS_TO_TES || m_caseType == CASETYPE_VS_TO_TCS_TO_TES;

    const string tesIn0 = tcsToTES ? "gl_in[0].gl_Position" : "in_te_attr[0]";
    const string tesIn1 = tcsToTES ? "gl_in[1].gl_Position" : "in_te_attr[1]";
    const string tesIn2 = tcsToTES ? "gl_in[2].gl_Position" : "in_te_attr[2]";

    std::string vertexShaderTemplate("${GLSL_VERSION_DECL}\n"
                                     "${GLSL_PER_VERTEX_OUT}\n"
                                     "\n"
                                     "in highp vec4 in_v_attr;\n" +
                                     string(!vsToTCS ? "out highp vec4 in_tc_attr;\n" : "") +
                                     "\n"
                                     "void main (void)\n"
                                     "{\n"
                                     "    " +
                                     (vsToTCS ? "gl_Position" : "in_tc_attr") +
                                     " = in_v_attr;\n"
                                     "}\n");
    std::string tessellationControlTemplate(
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT_ARR}\n"
        "\n"
        "layout (vertices = 3) out;\n"
        "\n" +
        string(!vsToTCS ? "in highp vec4 in_tc_attr[];\n" : "") + "\n" +
        (!tcsToTES ? "out highp vec4 in_te_attr[];\n" : "") +
        "\n"
        "void main (void)\n"
        "{\n"
        "    " +
        (tcsToTES ? "gl_out[gl_InvocationID].gl_Position" : "in_te_attr[gl_InvocationID]") + " = " +
        (vsToTCS ? "gl_in[gl_InvocationID].gl_Position" : "in_tc_attr[gl_InvocationID]") +
        ";\n"
        "\n"
        "    gl_TessLevelInner[0] = 2.0;\n"
        "    gl_TessLevelInner[1] = 3.0;\n"
        "\n"
        "    gl_TessLevelOuter[0] = 4.0;\n"
        "    gl_TessLevelOuter[1] = 5.0;\n"
        "    gl_TessLevelOuter[2] = 6.0;\n"
        "    gl_TessLevelOuter[3] = 7.0;\n"
        "}\n");
    std::string tessellationEvaluationTemplate("${GLSL_VERSION_DECL}\n"
                                               "${TESSELLATION_SHADER_REQUIRE}\n"
                                               "${GLSL_PER_VERTEX_IN_ARR}\n"
                                               "${GLSL_PER_VERTEX_OUT}\n"
                                               "\n" +
                                               getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_TRIANGLES) +
                                               "\n" + (!tcsToTES ? "in highp vec4 in_te_attr[];\n" : "") +
                                               "\n"
                                               "out highp vec4 in_f_color;\n"
                                               "\n"
                                               "void main (void)\n"
                                               "{\n"
                                               "    highp vec2 xy = gl_TessCoord.x * " +
                                               tesIn0 +
                                               ".xy\n"
                                               "                  + gl_TessCoord.y * " +
                                               tesIn1 +
                                               ".xy\n"
                                               "                  + gl_TessCoord.z * " +
                                               tesIn2 +
                                               ".xy;\n"
                                               "    gl_Position = vec4(xy, 0.0, 1.0);\n"
                                               "    in_f_color = vec4(" +
                                               tesIn0 + ".z + " + tesIn1 +
                                               ".w,\n"
                                               "                      " +
                                               tesIn2 + ".z + " + tesIn0 +
                                               ".w,\n"
                                               "                      " +
                                               tesIn1 + ".z + " + tesIn2 +
                                               ".w,\n"
                                               "                      1.0);\n"
                                               "}\n");
    std::string fragmentShaderTemplate("${GLSL_VERSION_DECL}\n"
                                       "\n"
                                       "layout (location = 0) out mediump vec4 o_color;\n"
                                       "\n"
                                       "in highp vec4 in_f_color;\n"
                                       "\n"
                                       "void main (void)\n"
                                       "{\n"
                                       "    o_color = in_f_color;\n"
                                       "}\n");

    m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderTemplate.c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, tessellationControlTemplate.c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, tessellationEvaluationTemplate.c_str()))
                              << glu::FragmentSource(specializeShader(m_context, fragmentShaderTemplate.c_str()))));

    m_testCtx.getLog() << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Program compilation failed");
}

void GLPositionCase::deinit(void)
{
    m_program.clear();
}

GLPositionCase::IterateResult GLPositionCase::iterate(void)
{
    TestLog &log                   = m_testCtx.getLog();
    const RenderContext &renderCtx = m_context.getRenderContext();
    const RandomViewport viewport(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
    const glw::Functions &gl = renderCtx.getFunctions();
    const uint32_t programGL = m_program->getProgram();

    static const float attributes[3 * 4] = {-0.8f, -0.7f, 0.1f, 0.7f, -0.5f, 0.4f, 0.2f, 0.5f, 0.3f, 0.2f, 0.3f, 0.45f};

    gl.useProgram(programGL);
    setViewport(gl, viewport);
    gl.patchParameteri(GL_PATCH_VERTICES, 3);

    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);

    log << TestLog::Message << "Note: input data for in_v_attr:\n" << arrayStr(attributes, 4) << TestLog::EndMessage;

    {
        const glu::VertexArrayBinding bindings[] = {glu::va::Float("in_v_attr", 4, 3, 0, &attributes[0])};
        glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(bindings), &bindings[0], glu::pr::Patches(3));

        {
            const tcu::Surface pixels         = getPixels(renderCtx, viewport);
            const tcu::TextureLevel reference = getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str());
            const bool success = tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(),
                                                   pixels.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

            if (!success)
            {
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
                return STOP;
            }
        }
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

class LimitQueryCase : public TestCase
{
public:
    LimitQueryCase(Context &context, const char *name, const char *desc, glw::GLenum target, int minValue);

private:
    IterateResult iterate(void);

    const glw::GLenum m_target;
    const int m_minValue;
};

LimitQueryCase::LimitQueryCase(Context &context, const char *name, const char *desc, glw::GLenum target, int minValue)
    : TestCase(context, name, desc)
    , m_target(target)
    , m_minValue(minValue)
{
}

LimitQueryCase::IterateResult LimitQueryCase::iterate(void)
{
    checkTessellationSupport(m_context);

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);
    verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_INTEGER);

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Types", "Alternative queries");
        verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_BOOLEAN);
        verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_INTEGER64);
        verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_FLOAT);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class CombinedUniformLimitCase : public TestCase
{
public:
    CombinedUniformLimitCase(Context &context, const char *name, const char *desc, glw::GLenum combined,
                             glw::GLenum numBlocks, glw::GLenum defaultComponents);

private:
    IterateResult iterate(void);

    const glw::GLenum m_combined;
    const glw::GLenum m_numBlocks;
    const glw::GLenum m_defaultComponents;
};

CombinedUniformLimitCase::CombinedUniformLimitCase(Context &context, const char *name, const char *desc,
                                                   glw::GLenum combined, glw::GLenum numBlocks,
                                                   glw::GLenum defaultComponents)
    : TestCase(context, name, desc)
    , m_combined(combined)
    , m_numBlocks(numBlocks)
    , m_defaultComponents(defaultComponents)
{
}

CombinedUniformLimitCase::IterateResult CombinedUniformLimitCase::iterate(void)
{
    checkTessellationSupport(m_context);

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);

    m_testCtx.getLog() << tcu::TestLog::Message << "The minimum value of " << glu::getGettableStateStr(m_combined)
                       << " is " << glu::getGettableStateStr(m_numBlocks) << " x MAX_UNIFORM_BLOCK_SIZE / 4 + "
                       << glu::getGettableStateStr(m_defaultComponents) << tcu::TestLog::EndMessage;

    StateQueryMemoryWriteGuard<glw::GLint> maxUniformBlocks;
    gl.glGetIntegerv(m_numBlocks, &maxUniformBlocks);
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv");

    StateQueryMemoryWriteGuard<glw::GLint> maxUniformBlockSize;
    gl.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv");

    StateQueryMemoryWriteGuard<glw::GLint> maxUniformComponents;
    gl.glGetIntegerv(m_defaultComponents, &maxUniformComponents);
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv");

    if (maxUniformBlocks.verifyValidity(result) && maxUniformBlockSize.verifyValidity(result) &&
        maxUniformComponents.verifyValidity(result))
    {
        const int limit = ((int)maxUniformBlocks) * ((int)maxUniformBlockSize) / 4 + (int)maxUniformComponents;
        verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_INTEGER);

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Types", "Alternative queries");
            verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_BOOLEAN);
            verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_INTEGER64);
            verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_FLOAT);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class PatchVerticesStateCase : public TestCase
{
public:
    PatchVerticesStateCase(Context &context, const char *name, const char *desc);

private:
    IterateResult iterate(void);
};

PatchVerticesStateCase::PatchVerticesStateCase(Context &context, const char *name, const char *desc)
    : TestCase(context, name, desc)
{
}

PatchVerticesStateCase::IterateResult PatchVerticesStateCase::iterate(void)
{
    checkTessellationSupport(m_context);

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);

    // initial
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial value");

        verifyStateInteger(result, gl, GL_PATCH_VERTICES, 3, QUERY_INTEGER);
    }

    // bind
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "set", "After set");

        gl.glPatchParameteri(GL_PATCH_VERTICES, 22);
        GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glPatchParameteri");

        verifyStateInteger(result, gl, GL_PATCH_VERTICES, 22, QUERY_INTEGER);
        {
            const tcu::ScopedLogSection subsection(m_testCtx.getLog(), "Types", "Alternative queries");
            verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_BOOLEAN);
            verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_INTEGER64);
            verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_FLOAT);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class PrimitiveRestartForPatchesSupportedCase : public TestCase
{
public:
    PrimitiveRestartForPatchesSupportedCase(Context &context, const char *name, const char *desc);

private:
    IterateResult iterate(void);
};

PrimitiveRestartForPatchesSupportedCase::PrimitiveRestartForPatchesSupportedCase(Context &context, const char *name,
                                                                                 const char *desc)
    : TestCase(context, name, desc)
{
}

PrimitiveRestartForPatchesSupportedCase::IterateResult PrimitiveRestartForPatchesSupportedCase::iterate(void)
{
    checkTessellationSupport(m_context);

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    QueriedState state;

    gl.enableLogging(true);

    queryState(result, gl, QUERY_BOOLEAN, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state);

    if (!state.isUndefined())
    {
        const tcu::ScopedLogSection subsection(m_testCtx.getLog(), "Types", "Alternative types");
        verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(),
                           QUERY_INTEGER);
        verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(),
                           QUERY_INTEGER64);
        verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(), QUERY_FLOAT);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class TessProgramQueryCase : public TestCase
{
public:
    TessProgramQueryCase(Context &context, const char *name, const char *desc);

    std::string getVertexSource(void) const;
    std::string getFragmentSource(void) const;
    std::string getTessCtrlSource(const char *globalLayouts) const;
    std::string getTessEvalSource(const char *globalLayouts) const;
};

TessProgramQueryCase::TessProgramQueryCase(Context &context, const char *name, const char *desc)
    : TestCase(context, name, desc)
{
}

std::string TessProgramQueryCase::getVertexSource(void) const
{
    return "${GLSL_VERSION_DECL}\n"
           "${GLSL_PER_VERTEX_OUT}\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = vec4(float(gl_VertexID), float(gl_VertexID / 2), 0.0, 1.0);\n"
           "}\n";
}

std::string TessProgramQueryCase::getFragmentSource(void) const
{
    return "${GLSL_VERSION_DECL}\n"
           "layout (location = 0) out mediump vec4 o_color;\n"
           "void main (void)\n"
           "{\n"
           "    o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "}\n";
}

std::string TessProgramQueryCase::getTessCtrlSource(const char *globalLayouts) const
{
    return "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${GLSL_PER_VERTEX_IN_ARR}\n"
           "${GLSL_PER_VERTEX_OUT_ARR}\n" +
           std::string(globalLayouts) +
           ";\n"
           "void main (void)\n"
           "{\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
           "    gl_TessLevelInner[0] = 2.8;\n"
           "    gl_TessLevelInner[1] = 2.8;\n"
           "    gl_TessLevelOuter[0] = 2.8;\n"
           "    gl_TessLevelOuter[1] = 2.8;\n"
           "    gl_TessLevelOuter[2] = 2.8;\n"
           "    gl_TessLevelOuter[3] = 2.8;\n"
           "}\n";
}

std::string TessProgramQueryCase::getTessEvalSource(const char *globalLayouts) const
{
    return "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${GLSL_PER_VERTEX_IN_ARR}\n"
           "${GLSL_PER_VERTEX_OUT}\n" +
           std::string(globalLayouts) +
           ";\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = gl_TessCoord.x * gl_in[0].gl_Position\n"
           "                + gl_TessCoord.y * gl_in[1].gl_Position\n"
           "                + gl_TessCoord.y * gl_in[2].gl_Position\n"
           "                + gl_TessCoord.z * gl_in[3].gl_Position;\n"
           "}\n";
}

class TessControlOutputVerticesCase : public TessProgramQueryCase
{
public:
    TessControlOutputVerticesCase(Context &context, const char *name, const char *desc);

private:
    IterateResult iterate(void);
};

TessControlOutputVerticesCase::TessControlOutputVerticesCase(Context &context, const char *name, const char *desc)
    : TessProgramQueryCase(context, name, desc)
{
}

TessControlOutputVerticesCase::IterateResult TessControlOutputVerticesCase::iterate(void)
{
    checkTessellationSupport(m_context);

    glu::ShaderProgram program(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str()))
                              << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, getTessCtrlSource("layout(vertices=4) out").c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, getTessEvalSource("layout(triangles) in").c_str())));

    m_testCtx.getLog() << program;
    if (!program.isOk())
        throw tcu::TestError("failed to build program");

    {
        glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
        tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

        gl.enableLogging(true);
        verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_CONTROL_OUTPUT_VERTICES, 4,
                                  QUERY_PROGRAM_INTEGER);

        result.setTestContextResult(m_testCtx);
    }
    return STOP;
}

class TessGenModeQueryCase : public TessProgramQueryCase
{
public:
    TessGenModeQueryCase(Context &context, const char *name, const char *desc);

private:
    IterateResult iterate(void);
};

TessGenModeQueryCase::TessGenModeQueryCase(Context &context, const char *name, const char *desc)
    : TessProgramQueryCase(context, name, desc)
{
}

TessGenModeQueryCase::IterateResult TessGenModeQueryCase::iterate(void)
{
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    static const struct
    {
        const char *description;
        const char *layout;
        glw::GLenum mode;
    } s_modes[] = {
        {"Triangles", "layout(triangles) in", GL_TRIANGLES},
        {"Isolines", "layout(isolines) in", GL_ISOLINES},
        {"Quads", "layout(quads) in", GL_QUADS},
    };

    checkTessellationSupport(m_context);
    gl.enableLogging(true);

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

        glu::ShaderProgram program(m_context.getRenderContext(),
                                   glu::ProgramSources()
                                       << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str()))
                                       << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str()))
                                       << glu::TessellationControlSource(specializeShader(
                                              m_context, getTessCtrlSource("layout(vertices=6) out").c_str()))
                                       << glu::TessellationEvaluationSource(specializeShader(
                                              m_context, getTessEvalSource(s_modes[ndx].layout).c_str())));

        m_testCtx.getLog() << program;
        if (!program.isOk())
            result.fail("failed to build program");
        else
            verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_MODE, s_modes[ndx].mode,
                                      QUERY_PROGRAM_INTEGER);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class TessGenSpacingQueryCase : public TessProgramQueryCase
{
public:
    TessGenSpacingQueryCase(Context &context, const char *name, const char *desc);

private:
    IterateResult iterate(void);
};

TessGenSpacingQueryCase::TessGenSpacingQueryCase(Context &context, const char *name, const char *desc)
    : TessProgramQueryCase(context, name, desc)
{
}

TessGenSpacingQueryCase::IterateResult TessGenSpacingQueryCase::iterate(void)
{
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    static const struct
    {
        const char *description;
        const char *layout;
        glw::GLenum spacing;
    } s_modes[] = {
        {"Default spacing", "layout(triangles) in", GL_EQUAL},
        {"Equal spacing", "layout(triangles, equal_spacing) in", GL_EQUAL},
        {"Fractional even spacing", "layout(triangles, fractional_even_spacing) in", GL_FRACTIONAL_EVEN},
        {"Fractional odd spacing", "layout(triangles, fractional_odd_spacing) in", GL_FRACTIONAL_ODD},
    };

    checkTessellationSupport(m_context);
    gl.enableLogging(true);

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

        glu::ShaderProgram program(m_context.getRenderContext(),
                                   glu::ProgramSources()
                                       << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str()))
                                       << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str()))
                                       << glu::TessellationControlSource(specializeShader(
                                              m_context, getTessCtrlSource("layout(vertices=6) out").c_str()))
                                       << glu::TessellationEvaluationSource(specializeShader(
                                              m_context, getTessEvalSource(s_modes[ndx].layout).c_str())));

        m_testCtx.getLog() << program;
        if (!program.isOk())
            result.fail("failed to build program");
        else
            verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_SPACING, s_modes[ndx].spacing,
                                      QUERY_PROGRAM_INTEGER);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class TessGenVertexOrderQueryCase : public TessProgramQueryCase
{
public:
    TessGenVertexOrderQueryCase(Context &context, const char *name, const char *desc);

private:
    IterateResult iterate(void);
};

TessGenVertexOrderQueryCase::TessGenVertexOrderQueryCase(Context &context, const char *name, const char *desc)
    : TessProgramQueryCase(context, name, desc)
{
}

TessGenVertexOrderQueryCase::IterateResult TessGenVertexOrderQueryCase::iterate(void)
{
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    static const struct
    {
        const char *description;
        const char *layout;
        glw::GLenum order;
    } s_modes[] = {
        {"Default order", "layout(triangles) in", GL_CCW},
        {"CW order", "layout(triangles, cw) in", GL_CW},
        {"CCW order", "layout(triangles, ccw) in", GL_CCW},
    };

    checkTessellationSupport(m_context);
    gl.enableLogging(true);

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

        glu::ShaderProgram program(m_context.getRenderContext(),
                                   glu::ProgramSources()
                                       << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str()))
                                       << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str()))
                                       << glu::TessellationControlSource(specializeShader(
                                              m_context, getTessCtrlSource("layout(vertices=6) out").c_str()))
                                       << glu::TessellationEvaluationSource(specializeShader(
                                              m_context, getTessEvalSource(s_modes[ndx].layout).c_str())));

        m_testCtx.getLog() << program;
        if (!program.isOk())
            result.fail("failed to build program");
        else
            verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_VERTEX_ORDER, s_modes[ndx].order,
                                      QUERY_PROGRAM_INTEGER);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class TessGenPointModeQueryCase : public TessProgramQueryCase
{
public:
    TessGenPointModeQueryCase(Context &context, const char *name, const char *desc);

private:
    IterateResult iterate(void);
};

TessGenPointModeQueryCase::TessGenPointModeQueryCase(Context &context, const char *name, const char *desc)
    : TessProgramQueryCase(context, name, desc)
{
}

TessGenPointModeQueryCase::IterateResult TessGenPointModeQueryCase::iterate(void)
{
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    static const struct
    {
        const char *description;
        const char *layout;
        glw::GLenum mode;
    } s_modes[] = {
        {"Default mode", "layout(triangles) in", GL_FALSE},
        {"Point mode", "layout(triangles, point_mode) in", GL_TRUE},
    };

    checkTessellationSupport(m_context);
    gl.enableLogging(true);

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

        glu::ShaderProgram program(m_context.getRenderContext(),
                                   glu::ProgramSources()
                                       << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str()))
                                       << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str()))
                                       << glu::TessellationControlSource(specializeShader(
                                              m_context, getTessCtrlSource("layout(vertices=6) out").c_str()))
                                       << glu::TessellationEvaluationSource(specializeShader(
                                              m_context, getTessEvalSource(s_modes[ndx].layout).c_str())));

        m_testCtx.getLog() << program;
        if (!program.isOk())
            result.fail("failed to build program");
        else
            verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_POINT_MODE, s_modes[ndx].mode,
                                      QUERY_PROGRAM_INTEGER);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class ReferencedByTessellationQueryCase : public TestCase
{
public:
    ReferencedByTessellationQueryCase(Context &context, const char *name, const char *desc, bool isCtrlCase);

private:
    void init(void);
    IterateResult iterate(void);

    std::string getVertexSource(void) const;
    std::string getFragmentSource(void) const;
    std::string getTessCtrlSource(void) const;
    std::string getTessEvalSource(void) const;

    const bool m_isCtrlCase;
};

ReferencedByTessellationQueryCase::ReferencedByTessellationQueryCase(Context &context, const char *name,
                                                                     const char *desc, bool isCtrlCase)
    : TestCase(context, name, desc)
    , m_isCtrlCase(isCtrlCase)
{
}

void ReferencedByTessellationQueryCase::init(void)
{
    checkTessellationSupport(m_context);
}

ReferencedByTessellationQueryCase::IterateResult ReferencedByTessellationQueryCase::iterate(void)
{
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    glu::ShaderProgram program(m_context.getRenderContext(),
                               glu::ProgramSources()
                                   << glu::VertexSource(specializeShader(m_context, getVertexSource().c_str()))
                                   << glu::FragmentSource(specializeShader(m_context, getFragmentSource().c_str()))
                                   << glu::TessellationControlSource(
                                          specializeShader(m_context, getTessCtrlSource().c_str()))
                                   << glu::TessellationEvaluationSource(
                                          specializeShader(m_context, getTessEvalSource().c_str())));

    gl.enableLogging(true);

    m_testCtx.getLog() << program;
    if (!program.isOk())
        result.fail("failed to build program");
    else
    {
        const uint32_t props[1] = {(uint32_t)((m_isCtrlCase) ? (GL_REFERENCED_BY_TESS_CONTROL_SHADER) :
                                                               (GL_REFERENCED_BY_TESS_EVALUATION_SHADER))};

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "UnreferencedUniform",
                                                "Unreferenced uniform u_unreferenced");
            uint32_t resourcePos;
            glw::GLsizei length   = 0;
            glw::GLint referenced = 0;

            resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_unreferenced");
            m_testCtx.getLog() << tcu::TestLog::Message << "u_unreferenced resource index: " << resourcePos
                               << tcu::TestLog::EndMessage;

            if (resourcePos == GL_INVALID_INDEX)
                result.fail("resourcePos was GL_INVALID_INDEX");
            else
            {
                gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length,
                                          &referenced);
                m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0])
                                   << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
                                   << tcu::TestLog::EndMessage;

                GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "query resource");

                if (length == 0 || referenced != GL_FALSE)
                    result.fail("expected GL_FALSE");
            }
        }

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "ReferencedUniform",
                                                "Referenced uniform u_referenced");
            uint32_t resourcePos;
            glw::GLsizei length   = 0;
            glw::GLint referenced = 0;

            resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_referenced");
            m_testCtx.getLog() << tcu::TestLog::Message << "u_referenced resource index: " << resourcePos
                               << tcu::TestLog::EndMessage;

            if (resourcePos == GL_INVALID_INDEX)
                result.fail("resourcePos was GL_INVALID_INDEX");
            else
            {
                gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length,
                                          &referenced);
                m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0])
                                   << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
                                   << tcu::TestLog::EndMessage;

                GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource");

                if (length == 0 || referenced != GL_TRUE)
                    result.fail("expected GL_TRUE");
            }
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

std::string ReferencedByTessellationQueryCase::getVertexSource(void) const
{
    return "${GLSL_VERSION_DECL}\n"
           "${GLSL_PER_VERTEX_OUT}\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = vec4(float(gl_VertexID), float(gl_VertexID / 2), 0.0, 1.0);\n"
           "}\n";
}

std::string ReferencedByTessellationQueryCase::getFragmentSource(void) const
{
    return "${GLSL_VERSION_DECL}\n"
           "layout (location = 0) out mediump vec4 o_color;\n"
           "void main (void)\n"
           "{\n"
           "    o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "}\n";
}

std::string ReferencedByTessellationQueryCase::getTessCtrlSource(void) const
{
    std::ostringstream buf;
    buf << "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${GLSL_PER_VERTEX_IN_ARR}\n"
           "${GLSL_PER_VERTEX_OUT_ARR}\n"
           "layout(vertices = 3) out;\n"
           "uniform highp vec4 "
        << ((m_isCtrlCase) ? ("u_referenced") : ("u_unreferenced"))
        << ";\n"
           "void main (void)\n"
           "{\n"
           "    vec4 offset = "
        << ((m_isCtrlCase) ? ("u_referenced") : ("u_unreferenced"))
        << ";\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position + offset;\n"
           "    gl_TessLevelInner[0] = 2.8;\n"
           "    gl_TessLevelInner[1] = 2.8;\n"
           "    gl_TessLevelOuter[0] = 2.8;\n"
           "    gl_TessLevelOuter[1] = 2.8;\n"
           "    gl_TessLevelOuter[2] = 2.8;\n"
           "    gl_TessLevelOuter[3] = 2.8;\n"
           "}\n";
    return buf.str();
}

std::string ReferencedByTessellationQueryCase::getTessEvalSource(void) const
{
    std::ostringstream buf;
    buf << "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${GLSL_PER_VERTEX_IN_ARR}\n"
           "${GLSL_PER_VERTEX_OUT}\n"
           "layout(triangles) in;\n"
           "uniform highp vec4 "
        << ((m_isCtrlCase) ? ("u_unreferenced") : ("u_referenced"))
        << ";\n"
           "void main (void)\n"
           "{\n"
           "    vec4 offset = "
        << ((m_isCtrlCase) ? ("u_unreferenced") : ("u_referenced"))
        << ";\n"
           "    gl_Position = gl_TessCoord.x * gl_in[0].gl_Position\n"
           "                + gl_TessCoord.y * gl_in[1].gl_Position\n"
           "                + gl_TessCoord.z * gl_in[2].gl_Position\n"
           "                + offset;\n"
           "}\n";

    return buf.str();
}

class IsPerPatchQueryCase : public TestCase
{
public:
    IsPerPatchQueryCase(Context &context, const char *name, const char *desc);

private:
    void init(void);
    IterateResult iterate(void);
};

IsPerPatchQueryCase::IsPerPatchQueryCase(Context &context, const char *name, const char *desc)
    : TestCase(context, name, desc)
{
}

void IsPerPatchQueryCase::init(void)
{
    checkTessellationSupport(m_context);
}

IsPerPatchQueryCase::IterateResult IsPerPatchQueryCase::iterate(void)
{
    static const char *const s_controlSource =
        "${GLSL_VERSION_DECL}\n"
        "${TESSELLATION_SHADER_REQUIRE}\n"
        "${GLSL_PER_VERTEX_IN_ARR}\n"
        "${GLSL_PER_VERTEX_OUT_ARR}\n"
        "layout(vertices = 3) out;\n"
        "patch out highp vec4 v_perPatch;\n"
        "out highp vec4 v_perVertex[];\n"
        "void main (void)\n"
        "{\n"
        "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
        "    v_perPatch = gl_in[0].gl_Position;\n"
        "    v_perVertex[gl_InvocationID] = -gl_in[gl_InvocationID].gl_Position;\n"
        "    gl_TessLevelInner[0] = 2.8;\n"
        "    gl_TessLevelInner[1] = 2.8;\n"
        "    gl_TessLevelOuter[0] = 2.8;\n"
        "    gl_TessLevelOuter[1] = 2.8;\n"
        "    gl_TessLevelOuter[2] = 2.8;\n"
        "    gl_TessLevelOuter[3] = 2.8;\n"
        "}\n";
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    glu::ShaderProgram program(m_context.getRenderContext(),
                               glu::ProgramSources()
                                   << glu::TessellationControlSource(specializeShader(m_context, s_controlSource))
                                   << glu::ProgramSeparable(true));

    gl.enableLogging(true);

    m_testCtx.getLog() << program;
    if (!program.isOk())
        result.fail("failed to build program");
    else
    {
        const uint32_t props[1] = {GL_IS_PER_PATCH};

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "PerPatchOutput", "Per patch v_perPatch");
            uint32_t resourcePos;
            glw::GLsizei length   = 0;
            glw::GLint referenced = 0;

            resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_PROGRAM_OUTPUT, "v_perPatch");
            m_testCtx.getLog() << tcu::TestLog::Message << "v_perPatch resource index: " << resourcePos
                               << tcu::TestLog::EndMessage;

            if (resourcePos == GL_INVALID_INDEX)
                result.fail("resourcePos was GL_INVALID_INDEX");
            else
            {
                gl.glGetProgramResourceiv(program.getProgram(), GL_PROGRAM_OUTPUT, resourcePos, 1, props, 1, &length,
                                          &referenced);
                m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0])
                                   << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
                                   << tcu::TestLog::EndMessage;

                GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "query resource");

                if (length == 0 || referenced != GL_TRUE)
                    result.fail("expected GL_TRUE");
            }
        }

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "PerVertexhOutput", "Per vertex v_perVertex");
            uint32_t resourcePos;
            glw::GLsizei length   = 0;
            glw::GLint referenced = 0;

            resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_PROGRAM_OUTPUT, "v_perVertex");
            m_testCtx.getLog() << tcu::TestLog::Message << "v_perVertex resource index: " << resourcePos
                               << tcu::TestLog::EndMessage;

            if (resourcePos == GL_INVALID_INDEX)
                result.fail("resourcePos was GL_INVALID_INDEX");
            else
            {
                gl.glGetProgramResourceiv(program.getProgram(), GL_PROGRAM_OUTPUT, resourcePos, 1, props, 1, &length,
                                          &referenced);
                m_testCtx.getLog() << tcu::TestLog::Message << "Query " << glu::getProgramResourcePropertyStr(props[0])
                                   << ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
                                   << tcu::TestLog::EndMessage;

                GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource");

                if (length == 0 || referenced != GL_FALSE)
                    result.fail("expected GL_FALSE");
            }
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

} // namespace

TessellationTests::TessellationTests(Context &context, bool isGL45)
    : TestCaseGroup(context, "tessellation", "Tessellation Tests")
    , m_isGL45(isGL45)
{
}

TessellationTests::~TessellationTests(void)
{
}

void TessellationTests::init(void)
{
    {
        tcu::TestCaseGroup *const queryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "Query tests");
        addChild(queryGroup);

        // new limits
        queryGroup->addChild(
            new LimitQueryCase(m_context, "max_patch_vertices", "Test MAX_PATCH_VERTICES", GL_MAX_PATCH_VERTICES, 32));
        queryGroup->addChild(
            new LimitQueryCase(m_context, "max_tess_gen_level", "Test MAX_TESS_GEN_LEVEL", GL_MAX_TESS_GEN_LEVEL, 64));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_uniform_components",
                                                "Test MAX_TESS_CONTROL_UNIFORM_COMPONENTS",
                                                GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS, 1024));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_uniform_components",
                                                "Test MAX_TESS_EVALUATION_UNIFORM_COMPONENTS",
                                                GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, 1024));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_texture_image_units",
                                                "Test MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS",
                                                GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, 16));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_texture_image_units",
                                                "Test MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS",
                                                GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, 16));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_output_components",
                                                "Test MAX_TESS_CONTROL_OUTPUT_COMPONENTS",
                                                GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS, 64));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_patch_components",
                                                "Test MAX_TESS_PATCH_COMPONENTS", GL_MAX_TESS_PATCH_COMPONENTS, 120));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_total_output_components",
                                                "Test MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS",
                                                GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS, 2048));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_output_components",
                                                "Test MAX_TESS_EVALUATION_OUTPUT_COMPONENTS",
                                                GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS, 64));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_uniform_blocks",
                                                "Test MAX_TESS_CONTROL_UNIFORM_BLOCKS",
                                                GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, 12));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_uniform_blocks",
                                                "Test MAX_TESS_EVALUATION_UNIFORM_BLOCKS",
                                                GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, 12));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_input_components",
                                                "Test MAX_TESS_CONTROL_INPUT_COMPONENTS",
                                                GL_MAX_TESS_CONTROL_INPUT_COMPONENTS, 64));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_input_components",
                                                "Test MAX_TESS_EVALUATION_INPUT_COMPONENTS",
                                                GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS, 64));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_atomic_counter_buffers",
                                                "Test MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS",
                                                GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS, 0));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_atomic_counter_buffers",
                                                "Test MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS",
                                                GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS, 0));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_atomic_counters",
                                                "Test MAX_TESS_CONTROL_ATOMIC_COUNTERS",
                                                GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS, 0));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_atomic_counters",
                                                "Test MAX_TESS_EVALUATION_ATOMIC_COUNTERS",
                                                GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS, 0));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_image_uniforms",
                                                "Test MAX_TESS_CONTROL_IMAGE_UNIFORMS",
                                                GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS, 0));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_image_uniforms",
                                                "Test MAX_TESS_EVALUATION_IMAGE_UNIFORMS",
                                                GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, 0));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_shader_storage_blocks",
                                                "Test MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS",
                                                GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, 0));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_shader_storage_blocks",
                                                "Test MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS",
                                                GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, 0));

        // modified limits
        queryGroup->addChild(new LimitQueryCase(m_context, "max_uniform_buffer_bindings",
                                                "Test MAX_UNIFORM_BUFFER_BINDINGS", GL_MAX_UNIFORM_BUFFER_BINDINGS,
                                                72));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_combined_uniform_blocks",
                                                "Test MAX_COMBINED_UNIFORM_BLOCKS", GL_MAX_COMBINED_UNIFORM_BLOCKS,
                                                60));
        queryGroup->addChild(new LimitQueryCase(m_context, "max_combined_texture_image_units",
                                                "Test MAX_COMBINED_TEXTURE_IMAGE_UNITS",
                                                GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 96));

        // combined limits
        queryGroup->addChild(new CombinedUniformLimitCase(
            m_context, "max_combined_tess_control_uniform_components",
            "Test MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS", GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS,
            GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS));
        queryGroup->addChild(new CombinedUniformLimitCase(
            m_context, "max_combined_tess_evaluation_uniform_components",
            "Test MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS", GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS,
            GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS));

        // features
        queryGroup->addChild(new PrimitiveRestartForPatchesSupportedCase(
            m_context, "primitive_restart_for_patches_supported", "Test PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED"));

        // states
        queryGroup->addChild(new PatchVerticesStateCase(m_context, "patch_vertices", "Test PATCH_VERTICES"));

        // program states
        queryGroup->addChild(new TessControlOutputVerticesCase(m_context, "tess_control_output_vertices",
                                                               "Test TESS_CONTROL_OUTPUT_VERTICES"));
        queryGroup->addChild(new TessGenModeQueryCase(m_context, "tess_gen_mode", "Test TESS_GEN_MODE"));
        queryGroup->addChild(new TessGenSpacingQueryCase(m_context, "tess_gen_spacing", "Test TESS_GEN_SPACING"));
        queryGroup->addChild(
            new TessGenVertexOrderQueryCase(m_context, "tess_gen_vertex_order", "Test TESS_GEN_VERTEX_ORDER"));
        queryGroup->addChild(
            new TessGenPointModeQueryCase(m_context, "tess_gen_point_mode", "Test TESS_GEN_POINT_MODE"));

        // resource queries
        queryGroup->addChild(new ReferencedByTessellationQueryCase(m_context, "referenced_by_tess_control_shader",
                                                                   "Test REFERENCED_BY_TESS_CONTROL_SHADER", true));
        queryGroup->addChild(new ReferencedByTessellationQueryCase(m_context, "referenced_by_tess_evaluation_shader",
                                                                   "Test REFERENCED_BY_TESS_EVALUATION_SHADER", false));
        queryGroup->addChild(new IsPerPatchQueryCase(m_context, "is_per_patch", "Test IS_PER_PATCH"));
    }

    {
        TestCaseGroup *const tessCoordGroup = new TestCaseGroup(
            m_context, "tesscoord", "Get tessellation coordinates with transform feedback and validate them");
        addChild(tessCoordGroup);

        for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
        {
            const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;

            for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
                tessCoordGroup->addChild(new TessCoordCase(m_context,
                                                           (string() + getTessPrimitiveTypeShaderName(primitiveType) +
                                                            "_" + getSpacingModeShaderName((SpacingMode)spacingI))
                                                               .c_str(),
                                                           "", primitiveType, (SpacingMode)spacingI));
        }
    }

    {
        TestCaseGroup *const windingGroup =
            new TestCaseGroup(m_context, "winding", "Test the cw and ccw input layout qualifiers");
        addChild(windingGroup);

        for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
        {
            const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
            if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
                continue;

            for (int windingI = 0; windingI < WINDING_LAST; windingI++)
            {
                const Winding winding = (Winding)windingI;
                windingGroup->addChild(new WindingCase(
                    m_context,
                    (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + getWindingShaderName(winding))
                        .c_str(),
                    "", primitiveType, winding));
            }
        }
    }

    {
        TestCaseGroup *const shaderInputOutputGroup = new TestCaseGroup(
            m_context, "shader_input_output", "Test tessellation control and evaluation shader inputs and outputs");
        addChild(shaderInputOutputGroup);

        {
            static const struct
            {
                int inPatchSize;
                int outPatchSize;
            } patchVertexCountCases[] = {{5, 10}, {10, 5}};

            for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(patchVertexCountCases); caseNdx++)
            {
                const int inSize  = patchVertexCountCases[caseNdx].inPatchSize;
                const int outSize = patchVertexCountCases[caseNdx].outPatchSize;

                const string caseName =
                    "patch_vertices_" + de::toString(inSize) + "_in_" + de::toString(outSize) + "_out";

                shaderInputOutputGroup->addChild(
                    new PatchVertexCountCase(m_context, caseName.c_str(), "Test input and output patch vertex counts",
                                             inSize, outSize, ("data/tessellation/" + caseName + "_ref.png").c_str()));
            }
        }

        for (int caseTypeI = 0; caseTypeI < PerPatchDataCase::CASETYPE_LAST; caseTypeI++)
        {
            const PerPatchDataCase::CaseType caseType = (PerPatchDataCase::CaseType)caseTypeI;
            const char *const caseName                = PerPatchDataCase::getCaseTypeName(caseType);

            shaderInputOutputGroup->addChild(
                new PerPatchDataCase(m_context, caseName, PerPatchDataCase::getCaseTypeDescription(caseType), caseType,
                                     PerPatchDataCase::caseTypeUsesRefImageFromFile(caseType) ?
                                         (string() + "data/tessellation/" + caseName + "_ref.png").c_str() :
                                         DE_NULL));
        }

        for (int caseTypeI = 0; caseTypeI < GLPositionCase::CASETYPE_LAST; caseTypeI++)
        {
            const GLPositionCase::CaseType caseType = (GLPositionCase::CaseType)caseTypeI;
            const char *const caseName              = GLPositionCase::getCaseTypeName(caseType);

            shaderInputOutputGroup->addChild(
                new GLPositionCase(m_context, caseName, "", caseType, "data/tessellation/gl_position_ref.png"));
        }

        shaderInputOutputGroup->addChild(
            new BarrierCase(m_context, "barrier", "Basic barrier usage", "data/tessellation/barrier_ref.png"));
    }

    {
        TestCaseGroup *const miscDrawGroup =
            new TestCaseGroup(m_context, "misc_draw", "Miscellaneous draw-result-verifying cases");
        addChild(miscDrawGroup);

        for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
        {
            const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
            if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
                continue;

            const char *const primTypeName = getTessPrimitiveTypeShaderName(primitiveType);

            for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
            {
                const string caseName =
                    string() + "fill_cover_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI);

                miscDrawGroup->addChild(new BasicTriangleFillCoverCase(
                    m_context, caseName.c_str(),
                    "Check that there are no obvious gaps in the triangle-filled area of a tessellated shape",
                    primitiveType, (SpacingMode)spacingI, ("data/tessellation/" + caseName + "_ref").c_str()));
            }
        }

        for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
        {
            const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
            if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
                continue;

            const char *const primTypeName = getTessPrimitiveTypeShaderName(primitiveType);

            for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
            {
                const string caseName =
                    string() + "fill_overlap_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI);

                miscDrawGroup->addChild(new BasicTriangleFillNonOverlapCase(
                    m_context, caseName.c_str(),
                    "Check that there are no obvious triangle overlaps in the triangle-filled area of a tessellated "
                    "shape",
                    primitiveType, (SpacingMode)spacingI, ("data/tessellation/" + caseName + "_ref").c_str()));
            }
        }

        for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
        {
            const string caseName = string() + "isolines_" + getSpacingModeShaderName((SpacingMode)spacingI);

            miscDrawGroup->addChild(new IsolinesRenderCase(m_context, caseName.c_str(), "Basic isolines render test",
                                                           (SpacingMode)spacingI,
                                                           ("data/tessellation/" + caseName + "_ref").c_str()));
        }
    }

    {
        TestCaseGroup *const commonEdgeGroup = new TestCaseGroup(
            m_context, "common_edge", "Draw multiple adjacent shapes and check that no cracks appear between them");
        addChild(commonEdgeGroup);

        for (int caseTypeI = 0; caseTypeI < CommonEdgeCase::CASETYPE_LAST; caseTypeI++)
        {
            for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
            {
                const CommonEdgeCase::CaseType caseType = (CommonEdgeCase::CaseType)caseTypeI;
                const TessPrimitiveType primitiveType   = (TessPrimitiveType)primitiveTypeI;
                if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
                    continue;

                for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
                {
                    const SpacingMode spacing = (SpacingMode)spacingI;
                    const string caseName     = (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" +
                                             getSpacingModeShaderName(spacing) +
                                             (caseType == CommonEdgeCase::CASETYPE_BASIC   ? "" :
                                                  caseType == CommonEdgeCase::CASETYPE_PRECISE ? "_precise" :
                                                                                                 DE_NULL));

                    commonEdgeGroup->addChild(
                        new CommonEdgeCase(m_context, caseName.c_str(), "", primitiveType, spacing, caseType));
                }
            }
        }
    }

    {
        TestCaseGroup *const fractionalSpacingModeGroup =
            new TestCaseGroup(m_context, "fractional_spacing", "Test fractional spacing modes");
        addChild(fractionalSpacingModeGroup);

        fractionalSpacingModeGroup->addChild(
            new FractionalSpacingModeCase(m_context, "odd", "", SPACINGMODE_FRACTIONAL_ODD));
        fractionalSpacingModeGroup->addChild(
            new FractionalSpacingModeCase(m_context, "even", "", SPACINGMODE_FRACTIONAL_EVEN));
    }

    {
        TestCaseGroup *const primitiveDiscardGroup = new TestCaseGroup(
            m_context, "primitive_discard", "Test primitive discard with relevant outer tessellation level <= 0.0");
        addChild(primitiveDiscardGroup);

        for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
        {
            for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
            {
                for (int windingI = 0; windingI < WINDING_LAST; windingI++)
                {
                    for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
                    {
                        const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
                        const SpacingMode spacing             = (SpacingMode)spacingI;
                        const Winding winding                 = (Winding)windingI;
                        const bool usePointMode               = usePointModeI != 0;

                        primitiveDiscardGroup->addChild(new PrimitiveDiscardCase(
                            m_context,
                            (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" +
                             getSpacingModeShaderName(spacing) + "_" + getWindingShaderName(winding) +
                             (usePointMode ? "_point_mode" : ""))
                                .c_str(),
                            "", primitiveType, spacing, winding, usePointMode));
                    }
                }
            }
        }
    }

    {
        TestCaseGroup *const invarianceGroup =
            new TestCaseGroup(m_context, "invariance", "Test tessellation invariance rules");

        TestCaseGroup *const invariantPrimitiveSetGroup =
            new TestCaseGroup(m_context, "primitive_set", "Test invariance rule #1");
        TestCaseGroup *const invariantOuterEdgeGroup =
            new TestCaseGroup(m_context, "outer_edge_division", "Test invariance rule #2");
        TestCaseGroup *const symmetricOuterEdgeGroup =
            new TestCaseGroup(m_context, "outer_edge_symmetry", "Test invariance rule #3");
        TestCaseGroup *const outerEdgeVertexSetIndexIndependenceGroup =
            new TestCaseGroup(m_context, "outer_edge_index_independence", "Test invariance rule #4");
        TestCaseGroup *const invariantTriangleSetGroup =
            new TestCaseGroup(m_context, "triangle_set", "Test invariance rule #5");
        TestCaseGroup *const invariantInnerTriangleSetGroup =
            new TestCaseGroup(m_context, "inner_triangle_set", "Test invariance rule #6");
        TestCaseGroup *const invariantOuterTriangleSetGroup =
            new TestCaseGroup(m_context, "outer_triangle_set", "Test invariance rule #7");
        TestCaseGroup *const tessCoordComponentRangeGroup =
            new TestCaseGroup(m_context, "tess_coord_component_range", "Test invariance rule #8, first part");
        TestCaseGroup *const oneMinusTessCoordComponentGroup =
            new TestCaseGroup(m_context, "one_minus_tess_coord_component", "Test invariance rule #8, second part");

        addChild(invarianceGroup);
        invarianceGroup->addChild(invariantPrimitiveSetGroup);
        invarianceGroup->addChild(invariantOuterEdgeGroup);
        invarianceGroup->addChild(symmetricOuterEdgeGroup);
        invarianceGroup->addChild(outerEdgeVertexSetIndexIndependenceGroup);
        invarianceGroup->addChild(invariantTriangleSetGroup);
        invarianceGroup->addChild(invariantInnerTriangleSetGroup);
        invarianceGroup->addChild(invariantOuterTriangleSetGroup);
        invarianceGroup->addChild(tessCoordComponentRangeGroup);
        invarianceGroup->addChild(oneMinusTessCoordComponentGroup);

        for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
        {
            const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
            const string primName                 = getTessPrimitiveTypeShaderName(primitiveType);
            const bool triOrQuad =
                primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS;

            for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
            {
                const SpacingMode spacing = (SpacingMode)spacingI;
                const string primSpacName = primName + "_" + getSpacingModeShaderName(spacing);

                if (triOrQuad)
                {
                    invariantOuterEdgeGroup->addChild(
                        new InvariantOuterEdgeCase(m_context, primSpacName.c_str(), "", primitiveType, spacing));
                    invariantTriangleSetGroup->addChild(
                        new InvariantTriangleSetCase(m_context, primSpacName.c_str(), "", primitiveType, spacing));
                    invariantInnerTriangleSetGroup->addChild(
                        new InvariantInnerTriangleSetCase(m_context, primSpacName.c_str(), "", primitiveType, spacing));
                    invariantOuterTriangleSetGroup->addChild(
                        new InvariantOuterTriangleSetCase(m_context, primSpacName.c_str(), "", primitiveType, spacing));
                }

                for (int windingI = 0; windingI < WINDING_LAST; windingI++)
                {
                    const Winding winding         = (Winding)windingI;
                    const string primSpacWindName = primSpacName + "_" + getWindingShaderName(winding);

                    for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
                    {
                        const bool usePointMode            = usePointModeI != 0;
                        const string primSpacWindPointName = primSpacWindName + (usePointMode ? "_point_mode" : "");

                        invariantPrimitiveSetGroup->addChild(
                            new InvariantPrimitiveSetCase(m_context, primSpacWindPointName.c_str(), "", primitiveType,
                                                          spacing, winding, usePointMode));
                        symmetricOuterEdgeGroup->addChild(
                            new SymmetricOuterEdgeCase(m_context, primSpacWindPointName.c_str(), "", primitiveType,
                                                       spacing, winding, usePointMode));
                        tessCoordComponentRangeGroup->addChild(
                            new TessCoordComponentRangeCase(m_context, primSpacWindPointName.c_str(), "", primitiveType,
                                                            spacing, winding, usePointMode));
                        oneMinusTessCoordComponentGroup->addChild(
                            new OneMinusTessCoordComponentCase(m_context, primSpacWindPointName.c_str(), "",
                                                               primitiveType, spacing, winding, usePointMode));

                        if (triOrQuad)
                            outerEdgeVertexSetIndexIndependenceGroup->addChild(
                                new OuterEdgeVertexSetIndexIndependenceCase(m_context, primSpacWindPointName.c_str(),
                                                                            "", primitiveType, spacing, winding,
                                                                            usePointMode));
                    }
                }
            }
        }
    }

    {
        static const struct
        {
            const char *name;
            const char *description;
            UserDefinedIOCase::IOType ioType;
        } ioCases[] = {
            {"per_patch", "Per-patch TCS outputs", UserDefinedIOCase::IO_TYPE_PER_PATCH},
            {"per_patch_array", "Per-patch array TCS outputs", UserDefinedIOCase::IO_TYPE_PER_PATCH_ARRAY},
            {"per_patch_block", "Per-patch TCS outputs in IO block", UserDefinedIOCase::IO_TYPE_PER_PATCH_BLOCK},
            {"per_patch_block_array", "Per-patch TCS outputs in IO block array",
             UserDefinedIOCase::IO_TYPE_PER_PATCH_BLOCK_ARRAY},
            {"per_vertex", "Per-vertex TCS outputs", UserDefinedIOCase::IO_TYPE_PER_VERTEX},
            {"per_vertex_block", "Per-vertex TCS outputs in IO block", UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK},
        };

        TestCaseGroup *const userDefinedIOGroup = new TestCaseGroup(
            m_context, "user_defined_io", "Test non-built-in per-patch and per-vertex inputs and outputs");
        addChild(userDefinedIOGroup);

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ioCases); ++ndx)
        {
            TestCaseGroup *const ioTypeGroup =
                new TestCaseGroup(m_context, ioCases[ndx].name, ioCases[ndx].description);
            userDefinedIOGroup->addChild(ioTypeGroup);

            for (int vertexArraySizeI = 0; vertexArraySizeI < UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_LAST;
                 vertexArraySizeI++)
            {
                const UserDefinedIOCase::VertexIOArraySize vertexArraySize =
                    (UserDefinedIOCase::VertexIOArraySize)vertexArraySizeI;
                TestCaseGroup *const vertexArraySizeGroup = new TestCaseGroup(
                    m_context,
                    vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_IMPLICIT ?
                        "vertex_io_array_size_implicit" :
                    vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ?
                        "vertex_io_array_size_shader_builtin" :
                    vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY ?
                        "vertex_io_array_size_query" :
                        DE_NULL,
                    "");
                ioTypeGroup->addChild(vertexArraySizeGroup);

                for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
                {
                    const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
                    vertexArraySizeGroup->addChild(new UserDefinedIOCase(
                        m_context, getTessPrimitiveTypeShaderName(primitiveType), "", primitiveType,
                        ioCases[ndx].ioType, vertexArraySize, UserDefinedIOCase::TESS_CONTROL_OUT_ARRAY_SIZE_IMPLICIT,
                        (string() + "data/tessellation/user_defined_io_" +
                         getTessPrimitiveTypeShaderName(primitiveType) + "_ref.png")
                            .c_str()));
                }

                if (ioCases[ndx].ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX ||
                    ioCases[ndx].ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK)
                {
                    for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
                    {
                        const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
                        vertexArraySizeGroup->addChild(new UserDefinedIOCase(
                            m_context,
                            (string(getTessPrimitiveTypeShaderName(primitiveType)) + "_explicit_tcs_out_size").c_str(),
                            "", primitiveType, ioCases[ndx].ioType, vertexArraySize,
                            UserDefinedIOCase::TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT,
                            (string() + "data/tessellation/user_defined_io_" +
                             getTessPrimitiveTypeShaderName(primitiveType) + "_ref.png")
                                .c_str()));
                    }
                }
            }
        }

        // ES only
        if (!m_isGL45)
        {
            de::MovePtr<TestCaseGroup> negativeGroup(new TestCaseGroup(m_context, "negative", "Negative cases"));

            {
                de::MovePtr<TestCaseGroup> es31Group(
                    new TestCaseGroup(m_context, "es31", "GLSL ES 3.1 Negative cases"));
                gls::ShaderLibrary shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
                const std::vector<tcu::TestNode *> children =
                    shaderLibrary.loadShaderFile("shaders/es31/tessellation_negative_user_defined_io.test");

                for (int i = 0; i < (int)children.size(); i++)
                    es31Group->addChild(children[i]);

                negativeGroup->addChild(es31Group.release());
            }

            {
                de::MovePtr<TestCaseGroup> es32Group(
                    new TestCaseGroup(m_context, "es32", "GLSL ES 3.2 Negative cases"));
                gls::ShaderLibrary shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
                const std::vector<tcu::TestNode *> children =
                    shaderLibrary.loadShaderFile("shaders/es32/tessellation_negative_user_defined_io.test");

                for (int i = 0; i < (int)children.size(); i++)
                    es32Group->addChild(children[i]);

                negativeGroup->addChild(es32Group.release());
            }

            userDefinedIOGroup->addChild(negativeGroup.release());
        }
    }
}

} // namespace Functional
} // namespace gles31
} // namespace deqp
