/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2015 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 Primitive bounding box tests.
 *//*--------------------------------------------------------------------*/

#include "es31fPrimitiveBoundingBoxTests.hpp"

#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuSurface.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluContextInfo.hpp"
#include "gluRenderContext.hpp"
#include "gluStrUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "glsStateQueryUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deRandom.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"

#include <vector>
#include <sstream>
#include <algorithm>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

namespace StateQueryUtil = ::deqp::gls::StateQueryUtil;

struct BoundingBox
{
    tcu::Vec4 min;
    tcu::Vec4 max;

    /*--------------------------------------------------------------------*//*!
     * Get component by index of a 8-component vector constructed by
     * concatenating 4-component min and max vectors.
     *//*--------------------------------------------------------------------*/
    float &getComponentAccess(int ndx);
    const float &getComponentAccess(int ndx) const;
};

float &BoundingBox::getComponentAccess(int ndx)
{
    DE_ASSERT(ndx >= 0 && ndx < 8);
    if (ndx < 4)
        return min[ndx];
    else
        return max[ndx - 4];
}

const float &BoundingBox::getComponentAccess(int ndx) const
{
    return const_cast<BoundingBox *>(this)->getComponentAccess(ndx);
}

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

static ProjectedBBox projectBoundingBox(const BoundingBox &bbox)
{
    const float wMin = de::max(0.0f, bbox.min.w()); // clamp to w=0 as extension requires
    const float wMax = de::max(0.0f, bbox.max.w());
    ProjectedBBox retVal;

    retVal.min = tcu::min(bbox.min.swizzle(0, 1, 2) / wMin, bbox.min.swizzle(0, 1, 2) / wMax);
    retVal.max = tcu::max(bbox.max.swizzle(0, 1, 2) / wMin, bbox.max.swizzle(0, 1, 2) / wMax);
    return retVal;
}

static tcu::IVec4 getViewportBoundingBoxArea(const ProjectedBBox &bbox, const tcu::IVec2 &viewportSize,
                                             float size = 0.0f)
{
    tcu::Vec4 vertexBox;
    tcu::IVec4 pixelBox;

    vertexBox.x() = (bbox.min.x() * 0.5f + 0.5f) * (float)viewportSize.x();
    vertexBox.y() = (bbox.min.y() * 0.5f + 0.5f) * (float)viewportSize.y();
    vertexBox.z() = (bbox.max.x() * 0.5f + 0.5f) * (float)viewportSize.x();
    vertexBox.w() = (bbox.max.y() * 0.5f + 0.5f) * (float)viewportSize.y();

    pixelBox.x() = deFloorFloatToInt32(vertexBox.x() - size / 2.0f);
    pixelBox.y() = deFloorFloatToInt32(vertexBox.y() - size / 2.0f);
    pixelBox.z() = deCeilFloatToInt32(vertexBox.z() + size / 2.0f);
    pixelBox.w() = deCeilFloatToInt32(vertexBox.w() + size / 2.0f);
    return pixelBox;
}

static std::string specializeShader(Context &context, const char *code)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(context.getRenderContext().getType());
    std::map<std::string, std::string> specializationMap;

    specializationMap["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);

    if (glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::es(3, 2)))
    {
        specializationMap["ARB_ES32_COMPATIBILITY_REQUIRE"]  = "";
        specializationMap["GEOMETRY_SHADER_REQUIRE"]         = "";
        specializationMap["GEOMETRY_POINT_SIZE"]             = "#extension GL_EXT_geometry_point_size : require";
        specializationMap["GPU_SHADER5_REQUIRE"]             = "";
        specializationMap["TESSELLATION_SHADER_REQUIRE"]     = "";
        specializationMap["TESSELLATION_POINT_SIZE_REQUIRE"] = "#extension GL_EXT_tessellation_point_size : require";
        specializationMap["PRIMITIVE_BOUNDING_BOX_REQUIRE"]  = "";
        specializationMap["PRIM_GL_BOUNDING_BOX"]            = "gl_BoundingBox";
    }
    else if (glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::core(4, 5)))
    {
        specializationMap["ARB_ES32_COMPATIBILITY_REQUIRE"]  = "#extension GL_ARB_ES3_2_compatibility : require";
        specializationMap["GEOMETRY_SHADER_REQUIRE"]         = "";
        specializationMap["GEOMETRY_POINT_SIZE"]             = "";
        specializationMap["GPU_SHADER5_REQUIRE"]             = "";
        specializationMap["TESSELLATION_SHADER_REQUIRE"]     = "";
        specializationMap["TESSELLATION_POINT_SIZE_REQUIRE"] = "";
        specializationMap["PRIMITIVE_BOUNDING_BOX_REQUIRE"]  = "";
        specializationMap["PRIM_GL_BOUNDING_BOX"]            = "gl_BoundingBox";
    }
    else
    {
        specializationMap["ARB_ES32_COMPATIBILITY_REQUIRE"]  = "";
        specializationMap["GEOMETRY_SHADER_REQUIRE"]         = "#extension GL_EXT_geometry_shader : require";
        specializationMap["GEOMETRY_POINT_SIZE"]             = "#extension GL_EXT_geometry_point_size : require";
        specializationMap["GPU_SHADER5_REQUIRE"]             = "#extension GL_EXT_gpu_shader5 : require";
        specializationMap["TESSELLATION_SHADER_REQUIRE"]     = "#extension GL_EXT_tessellation_shader : require";
        specializationMap["TESSELLATION_POINT_SIZE_REQUIRE"] = "#extension GL_EXT_tessellation_point_size : require";
        specializationMap["PRIMITIVE_BOUNDING_BOX_REQUIRE"]  = "#extension GL_EXT_primitive_bounding_box : require";
        specializationMap["PRIM_GL_BOUNDING_BOX"]            = "gl_BoundingBoxEXT";
    }

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

static decltype(glw::Functions::primitiveBoundingBox) getBoundingBoxFunction(Context &context)
{
    decltype(glw::Functions::primitiveBoundingBox) boundingBoxFunc;
    const glw::Functions &funcs = context.getRenderContext().getFunctions();

    /* OpenGL ES is assumed to have it (extensions checks passed). */
    if (glu::isContextTypeES(context.getRenderContext().getType()))
        return funcs.primitiveBoundingBox;

    boundingBoxFunc = (decltype(boundingBoxFunc))context.getRenderContext().getProcAddress("glPrimitiveBoundingBoxARB");

    DE_ASSERT(boundingBoxFunc);

    return boundingBoxFunc;
}

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

static bool boundingBoxSupported(Context &context)
{
    /* Require one of:
     *    - OpenGL ES 3.2
     *    - OpenGL 4.5 + GL_ARB_ES3_2_compatibility
     *    - OpenGL ES 3.1 + GL_EXT_primitive_bounding_box
     */
    return glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
           ((glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::core(4, 5)) &&
             context.getContextInfo().isExtensionSupported("GL_ARB_ES3_2_compatibility")) ||
            context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"));
}

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

    void init(void);
    IterateResult iterate(void);
};

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

void InitialValueCase::init(void)
{
    if (!boundingBoxSupported(m_context))
        throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
}

InitialValueCase::IterateResult InitialValueCase::iterate(void)
{
    StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat[8]> state;
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    gl.enableLogging(true);

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Querying GL_PRIMITIVE_BOUNDING_BOX_EXT, expecting (-1, -1, -1, 1) (1, 1, 1, 1)"
                       << tcu::TestLog::EndMessage;

    gl.glGetFloatv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

    if (!state.verifyValidity(m_testCtx))
        return STOP;

    m_testCtx.getLog() << tcu::TestLog::Message << "Got " << tcu::formatArray(&state[0], &state[8])
                       << tcu::TestLog::EndMessage;

    if ((state[0] != -1.0f) || (state[1] != -1.0f) || (state[2] != -1.0f) || (state[3] != 1.0f) || (state[4] != 1.0f) ||
        (state[5] != 1.0f) || (state[6] != 1.0f) || (state[7] != 1.0f))
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Error, unexpected value" << tcu::TestLog::EndMessage;

        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid initial value");
    }
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    return STOP;
}

class QueryCase : public TestCase
{
public:
    enum QueryMethod
    {
        QUERY_FLOAT = 0,
        QUERY_BOOLEAN,
        QUERY_INT,
        QUERY_INT64,

        QUERY_LAST
    };

    QueryCase(Context &context, const char *name, const char *desc, QueryMethod method);

private:
    void init(void);
    IterateResult iterate(void);

    bool verifyState(glu::CallLogWrapper &gl, const BoundingBox &bbox) const;

    const QueryMethod m_method;
};

QueryCase::QueryCase(Context &context, const char *name, const char *desc, QueryMethod method)
    : TestCase(context, name, desc)
    , m_method(method)
{
    DE_ASSERT(method < QUERY_LAST);
}

void QueryCase::init(void)
{
    if (!boundingBoxSupported(m_context))
        throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
}

QueryCase::IterateResult QueryCase::iterate(void)
{
    static const BoundingBox fixedCases[] = {
        {tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f)},
        {tcu::Vec4(-0.0f, -0.0f, -0.0f, -0.0f), tcu::Vec4(0.0f, 0.0f, 0.0f, -0.0f)},
        {tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, -1.0f)},
        {tcu::Vec4(2.0f, 2.0f, 2.0f, 2.0f), tcu::Vec4(1.5f, 1.5f, 1.5f, 1.0f)},
        {tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f), tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f)},
        {tcu::Vec4(1.0f, 1.0f, 1.0f, 0.3f), tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.2f)},
    };

    const int numRandomCases = 9;
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    de::Random rnd(0xDE3210);
    std::vector<BoundingBox> cases;

    cases.insert(cases.begin(), DE_ARRAY_BEGIN(fixedCases), DE_ARRAY_END(fixedCases));
    for (int ndx = 0; ndx < numRandomCases; ++ndx)
    {
        BoundingBox boundingBox;

        // parameter evaluation order is not guaranteed, cannot just do "max = (rand(), rand(), ...)
        for (int coordNdx = 0; coordNdx < 8; ++coordNdx)
            boundingBox.getComponentAccess(coordNdx) = rnd.getFloat(-4.0f, 4.0f);

        cases.push_back(boundingBox);
    }

    gl.enableLogging(true);
    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    auto boundingBoxFunc = getBoundingBoxFunction(m_context);

    for (int caseNdx = 0; caseNdx < (int)cases.size(); ++caseNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration", "Iteration " + de::toString(caseNdx + 1));
        const BoundingBox &boundingBox = cases[caseNdx];

        /* On desktop GL, we cannot use call wrapper here but must use resolved extension function. */
        if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
        {
            boundingBoxFunc(boundingBox.min.x(), boundingBox.min.y(), boundingBox.min.z(), boundingBox.min.w(),
                            boundingBox.max.x(), boundingBox.max.y(), boundingBox.max.z(), boundingBox.max.w());
        }
        else
        {
            gl.glPrimitiveBoundingBox(boundingBox.min.x(), boundingBox.min.y(), boundingBox.min.z(),
                                      boundingBox.min.w(), boundingBox.max.x(), boundingBox.max.y(),
                                      boundingBox.max.z(), boundingBox.max.w());
        }

        if (!verifyState(gl, boundingBox))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected query result");
    }

    return STOP;
}

bool QueryCase::verifyState(glu::CallLogWrapper &gl, const BoundingBox &bbox) const
{
    switch (m_method)
    {
    case QUERY_FLOAT:
    {
        StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat[8]> state;
        bool error = false;

        gl.glGetFloatv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

        if (!state.verifyValidity(m_testCtx))
            return false;

        m_testCtx.getLog() << tcu::TestLog::Message << "glGetFloatv returned " << tcu::formatArray(&state[0], &state[8])
                           << tcu::TestLog::EndMessage;

        for (int ndx = 0; ndx < 8; ++ndx)
            if (state[ndx] != bbox.getComponentAccess(ndx))
                error = true;

        if (error)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, unexpected value\n"
                               << "Expected [" << bbox.min.x() << ", " << bbox.min.y() << ", " << bbox.min.z() << ", "
                               << bbox.min.w() << ", " << bbox.max.x() << ", " << bbox.max.y() << ", " << bbox.max.z()
                               << ", " << bbox.max.w() << "]" << tcu::TestLog::EndMessage;
            return false;
        }
        return true;
    }

    case QUERY_INT:
    {
        StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint[8]> state;
        bool error = false;

        gl.glGetIntegerv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

        if (!state.verifyValidity(m_testCtx))
            return false;

        m_testCtx.getLog() << tcu::TestLog::Message << "glGetIntegerv returned "
                           << tcu::formatArray(&state[0], &state[8]) << tcu::TestLog::EndMessage;

        for (int ndx = 0; ndx < 8; ++ndx)
            if (state[ndx] !=
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint>(bbox.getComponentAccess(ndx)) &&
                state[ndx] !=
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint>(bbox.getComponentAccess(ndx)))
                error = true;

        if (error)
        {
            tcu::MessageBuilder builder(&m_testCtx.getLog());

            builder << "Error, unexpected value\n"
                    << "Expected [";

            for (int ndx = 0; ndx < 8; ++ndx)
            {
                const glw::GLint roundDown =
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint>(bbox.getComponentAccess(ndx));
                const glw::GLint roundUp =
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint>(bbox.getComponentAccess(ndx));

                if (ndx != 0)
                    builder << ", ";

                if (roundDown == roundUp)
                    builder << roundDown;
                else
                    builder << "{" << roundDown << ", " << roundUp << "}";
            }

            builder << "]" << tcu::TestLog::EndMessage;
            return false;
        }
        return true;
    }

    case QUERY_INT64:
    {
        StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint64[8]> state;
        bool error = false;

        gl.glGetInteger64v(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

        if (!state.verifyValidity(m_testCtx))
            return false;

        m_testCtx.getLog() << tcu::TestLog::Message << "glGetInteger64v returned "
                           << tcu::formatArray(&state[0], &state[8]) << tcu::TestLog::EndMessage;

        for (int ndx = 0; ndx < 8; ++ndx)
            if (state[ndx] !=
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint64>(bbox.getComponentAccess(ndx)) &&
                state[ndx] !=
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint64>(bbox.getComponentAccess(ndx)))
                error = true;

        if (error)
        {
            tcu::MessageBuilder builder(&m_testCtx.getLog());

            builder << "Error, unexpected value\n"
                    << "Expected [";

            for (int ndx = 0; ndx < 8; ++ndx)
            {
                const glw::GLint64 roundDown =
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint64>(bbox.getComponentAccess(ndx));
                const glw::GLint64 roundUp =
                    StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint64>(bbox.getComponentAccess(ndx));

                if (ndx != 0)
                    builder << ", ";

                if (roundDown == roundUp)
                    builder << roundDown;
                else
                    builder << "{" << roundDown << ", " << roundUp << "}";
            }

            builder << "]" << tcu::TestLog::EndMessage;
            return false;
        }
        return true;
    }

    case QUERY_BOOLEAN:
    {
        StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean[8]> state;
        bool error = false;

        gl.glGetBooleanv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

        if (!state.verifyValidity(m_testCtx))
            return false;

        m_testCtx.getLog() << tcu::TestLog::Message << "glGetBooleanv returned [" << glu::getBooleanStr(state[0])
                           << ", " << glu::getBooleanStr(state[1]) << ", " << glu::getBooleanStr(state[2]) << ", "
                           << glu::getBooleanStr(state[3]) << ", " << glu::getBooleanStr(state[4]) << ", "
                           << glu::getBooleanStr(state[5]) << ", " << glu::getBooleanStr(state[6]) << ", "
                           << glu::getBooleanStr(state[7]) << "]\n"
                           << tcu::TestLog::EndMessage;

        for (int ndx = 0; ndx < 8; ++ndx)
            if (state[ndx] != ((bbox.getComponentAccess(ndx) != 0.0f) ? (GL_TRUE) : (GL_FALSE)))
                error = true;

        if (error)
        {
            tcu::MessageBuilder builder(&m_testCtx.getLog());

            builder << "Error, unexpected value\n"
                    << "Expected [";

            for (int ndx = 0; ndx < 8; ++ndx)
            {
                if (ndx != 0)
                    builder << ", ";

                builder << ((bbox.getComponentAccess(ndx) != 0.0f) ? ("GL_TRUE") : ("GL_FALSE"));
            }

            builder << "]" << tcu::TestLog::EndMessage;
            return false;
        }
        return true;
    }

    default:
        DE_ASSERT(false);
        return true;
    }
}

class BBoxRenderCase : public TestCase
{
public:
    enum
    {
        FLAG_RENDERTARGET_DEFAULT = 1u << 0, //!< render to default renderbuffer
        FLAG_RENDERTARGET_FBO     = 1u << 1, //!< render to framebuffer object

        FLAG_BBOXSIZE_EQUAL   = 1u << 2, //!< set tight primitive bounding box
        FLAG_BBOXSIZE_LARGER  = 1u << 3, //!< set padded primitive bounding box
        FLAG_BBOXSIZE_SMALLER = 1u << 4, //!< set too small primitive bounding box

        FLAG_TESSELLATION = 1u << 5, //!< use tessellation shader
        FLAG_GEOMETRY     = 1u << 6, //!< use geometry shader

        FLAG_SET_BBOX_STATE     = 1u << 7, //!< set primitive bounding box using global state
        FLAG_SET_BBOX_OUTPUT    = 1u << 8, //!< set primitive bounding box using tessellation output
        FLAG_PER_PRIMITIVE_BBOX = 1u << 9, //!< set primitive bounding per primitive

        FLAGBIT_USER_BIT = 10u //!< bits N and and up are reserved for subclasses
    };

    BBoxRenderCase(Context &context, const char *name, const char *description, int numIterations, uint32_t flags);
    ~BBoxRenderCase(void);

protected:
    enum RenderTarget
    {
        RENDERTARGET_DEFAULT,
        RENDERTARGET_FBO,
    };
    enum BBoxSize
    {
        BBOXSIZE_EQUAL,
        BBOXSIZE_LARGER,
        BBOXSIZE_SMALLER,
    };

    enum
    {
        RENDER_TARGET_MIN_SIZE = 256,
        FBO_SIZE               = 512,
        MIN_VIEWPORT_SIZE      = 256,
        MAX_VIEWPORT_SIZE      = 512,
    };
    DE_STATIC_ASSERT(MIN_VIEWPORT_SIZE <= RENDER_TARGET_MIN_SIZE);

    enum
    {
        VA_POS_VEC_NDX     = 0,
        VA_COL_VEC_NDX     = 1,
        VA_NUM_ATTRIB_VECS = 2,
    };

    enum AABBRoundDirection
    {
        ROUND_INWARDS = 0,
        ROUND_OUTWARDS
    };

    struct IterationConfig
    {
        tcu::IVec2 viewportPos;
        tcu::IVec2 viewportSize;
        tcu::Vec2 patternPos;  //!< in NDC
        tcu::Vec2 patternSize; //!< in NDC
        BoundingBox bbox;
    };

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

    virtual std::string genVertexSource(void) const                 = 0;
    virtual std::string genFragmentSource(void) const               = 0;
    virtual std::string genTessellationControlSource(void) const    = 0;
    virtual std::string genTessellationEvaluationSource(void) const = 0;
    virtual std::string genGeometrySource(void) const               = 0;

    virtual IterationConfig generateConfig(int iteration, const tcu::IVec2 &renderTargetSize) const = 0;
    virtual void getAttributeData(std::vector<tcu::Vec4> &data) const                               = 0;
    virtual void renderTestPattern(const IterationConfig &config)                                   = 0;
    virtual void verifyRenderResult(const IterationConfig &config)                                  = 0;

    IterationConfig generateRandomConfig(int seed, const tcu::IVec2 &renderTargetSize) const;
    tcu::IVec4 getViewportPatternArea(const tcu::Vec2 &patternPos, const tcu::Vec2 &patternSize,
                                      const tcu::IVec2 &viewportSize, AABBRoundDirection roundDir) const;

    void setupRender(const IterationConfig &config);

    enum ShaderFunction
    {
        SHADER_FUNC_MIRROR_X,
        SHADER_FUNC_MIRROR_Y,
        SHADER_FUNC_INSIDE_BBOX,
    };

    const char *genShaderFunction(ShaderFunction func) const;

    const RenderTarget m_renderTarget;
    const BBoxSize m_bboxSize;
    const bool m_hasTessellationStage;
    const bool m_hasGeometryStage;
    const bool m_useGlobalState;
    const bool m_calcPerPrimitiveBBox;
    const int m_numIterations;

    de::MovePtr<glu::ShaderProgram> m_program;
    de::MovePtr<glu::Buffer> m_vbo;
    de::MovePtr<glu::Framebuffer> m_fbo;
    glw::GLuint m_vao;

    decltype(glw::Functions::primitiveBoundingBox) m_boundingBoxFunc;

private:
    std::vector<IterationConfig> m_iterationConfigs;
    int m_iteration;
};

BBoxRenderCase::BBoxRenderCase(Context &context, const char *name, const char *description, int numIterations,
                               uint32_t flags)
    : TestCase(context, name, description)
    , m_renderTarget((flags & FLAG_RENDERTARGET_DEFAULT) ? (RENDERTARGET_DEFAULT) : (RENDERTARGET_FBO))
    , m_bboxSize((flags & FLAG_BBOXSIZE_EQUAL)   ? (BBOXSIZE_EQUAL) :
                 (flags & FLAG_BBOXSIZE_SMALLER) ? (BBOXSIZE_SMALLER) :
                                                   (BBOXSIZE_LARGER))
    , m_hasTessellationStage((flags & FLAG_TESSELLATION) != 0)
    , m_hasGeometryStage((flags & FLAG_GEOMETRY) != 0)
    , m_useGlobalState((flags & FLAG_SET_BBOX_STATE) != 0)
    , m_calcPerPrimitiveBBox((flags & FLAG_PER_PRIMITIVE_BBOX) != 0)
    , m_numIterations(numIterations)
    , m_vao(0)
    , m_boundingBoxFunc(NULL)
    , m_iteration(0)
{
    // validate flags
    DE_ASSERT(
        (((m_renderTarget == RENDERTARGET_DEFAULT) ? (FLAG_RENDERTARGET_DEFAULT) : (0)) |
         ((m_renderTarget == RENDERTARGET_FBO) ? (FLAG_RENDERTARGET_FBO) : (0)) |
         ((m_bboxSize == BBOXSIZE_EQUAL) ? (FLAG_BBOXSIZE_EQUAL) : (0)) |
         ((m_bboxSize == BBOXSIZE_LARGER) ? (FLAG_BBOXSIZE_LARGER) : (0)) |
         ((m_bboxSize == BBOXSIZE_SMALLER) ? (FLAG_BBOXSIZE_SMALLER) : (0)) |
         ((m_hasTessellationStage) ? (FLAG_TESSELLATION) : (0)) | ((m_hasGeometryStage) ? (FLAG_GEOMETRY) : (0)) |
         ((m_useGlobalState) ? (FLAG_SET_BBOX_STATE) : (0)) | ((!m_useGlobalState) ? (FLAG_SET_BBOX_OUTPUT) : (0)) |
         ((m_calcPerPrimitiveBBox) ? (FLAG_PER_PRIMITIVE_BBOX) : (0))) == (flags & ((1u << FLAGBIT_USER_BIT) - 1)));

    DE_ASSERT(m_useGlobalState || m_hasTessellationStage); // using non-global state requires tessellation

    if (m_calcPerPrimitiveBBox)
    {
        DE_ASSERT(!m_useGlobalState);            // per-primitive test requires per-primitive (non-global) state
        DE_ASSERT(m_bboxSize == BBOXSIZE_EQUAL); // smaller is hard to verify, larger not interesting
    }
}

BBoxRenderCase::~BBoxRenderCase(void)
{
    deinit();
}

void BBoxRenderCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const tcu::IVec2 renderTargetSize =
        (m_renderTarget == RENDERTARGET_DEFAULT) ?
            (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) :
            (tcu::IVec2(FBO_SIZE, FBO_SIZE));
    const bool hasES32OrGL45 = supportsES32OrGL45(m_context);

    // requirements
    if (!boundingBoxSupported(m_context))
        throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
    if (!hasES32OrGL45 && m_hasTessellationStage &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
    if (!hasES32OrGL45 && m_hasGeometryStage &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_geometry_shader extension");
    if (m_renderTarget == RENDERTARGET_DEFAULT &&
        (renderTargetSize.x() < RENDER_TARGET_MIN_SIZE || renderTargetSize.y() < RENDER_TARGET_MIN_SIZE))
        throw tcu::NotSupportedError(std::string() + "Test requires " + de::toString<int>(RENDER_TARGET_MIN_SIZE) +
                                     "x" + de::toString<int>(RENDER_TARGET_MIN_SIZE) + " default framebuffer");

    // log case specifics
    m_testCtx.getLog() << tcu::TestLog::Message << "Setting primitive bounding box "
                       << ((m_calcPerPrimitiveBBox)         ? ("to exactly cover each generated primitive") :
                           (m_bboxSize == BBOXSIZE_EQUAL)   ? ("to exactly cover rendered grid") :
                           (m_bboxSize == BBOXSIZE_LARGER)  ? ("to cover the grid and include some padding") :
                           (m_bboxSize == BBOXSIZE_SMALLER) ? ("to cover only a subset of the grid") :
                                                              (DE_NULL))
                       << ".\n"
                       << "Rendering with vertex" << ((m_hasTessellationStage) ? ("-tessellation{ctrl,eval}") : (""))
                       << ((m_hasGeometryStage) ? ("-geometry") : ("")) << "-fragment program.\n"
                       << "Set bounding box using "
                       << ((m_useGlobalState) ? ("PRIMITIVE_BOUNDING_BOX_EXT state") : ("gl_BoundingBoxEXT output"))
                       << "\n"
                       << "Verifying rendering results are valid within the bounding box." << tcu::TestLog::EndMessage;

    // resources

    {
        glu::ProgramSources sources;
        sources << glu::VertexSource(specializeShader(m_context, genVertexSource().c_str()));
        sources << glu::FragmentSource(specializeShader(m_context, genFragmentSource().c_str()));

        if (m_hasTessellationStage)
            sources << glu::TessellationControlSource(
                           specializeShader(m_context, genTessellationControlSource().c_str()))
                    << glu::TessellationEvaluationSource(
                           specializeShader(m_context, genTessellationEvaluationSource().c_str()));
        if (m_hasGeometryStage)
            sources << glu::GeometrySource(specializeShader(m_context, genGeometrySource().c_str()));

        m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources));
        GLU_EXPECT_NO_ERROR(gl.getError(), "build program");

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "ShaderProgram", "Shader program");
            m_testCtx.getLog() << *m_program;
        }

        if (!m_program->isOk())
            throw tcu::TestError("failed to build program");
    }

    if (m_renderTarget == RENDERTARGET_FBO)
    {
        glu::Texture colorAttachment(m_context.getRenderContext());

        gl.bindTexture(GL_TEXTURE_2D, *colorAttachment);
        gl.texStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, FBO_SIZE, FBO_SIZE);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");

        m_fbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
        gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, **m_fbo);
        gl.framebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *colorAttachment, 0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "attach");

        // unbind to prevent texture name deletion from removing it from current fbo attachments
        gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }

    m_boundingBoxFunc = getBoundingBoxFunction(m_context);

    {
        std::vector<tcu::Vec4> data;

        getAttributeData(data);

        // Generate VAO for desktop OpenGL
        if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
        {
            gl.genVertexArrays(1, &m_vao);
            gl.bindVertexArray(m_vao);
        }

        m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));
        gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
        gl.bufferData(GL_ARRAY_BUFFER, (int)(data.size() * sizeof(tcu::Vec4)), &data[0], GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "create vbo");
    }

    // Iterations
    for (int iterationNdx = 0; iterationNdx < m_numIterations; ++iterationNdx)
        m_iterationConfigs.push_back(generateConfig(iterationNdx, renderTargetSize));
}

void BBoxRenderCase::deinit(void)
{
    m_program.clear();
    m_vbo.clear();
    m_fbo.clear();

    if (m_vao)
    {
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
        m_vao = 0;
    }
}

BBoxRenderCase::IterateResult BBoxRenderCase::iterate(void)
{
    const tcu::ScopedLogSection section(m_testCtx.getLog(),
                                        std::string() + "Iteration" + de::toString((int)m_iteration),
                                        std::string() + "Iteration " + de::toString((int)m_iteration + 1) + "/" +
                                            de::toString((int)m_iterationConfigs.size()));
    const IterationConfig &config = m_iterationConfigs[m_iteration];

    // default
    if (m_iteration == 0)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    renderTestPattern(config);
    verifyRenderResult(config);

    if (++m_iteration < (int)m_iterationConfigs.size())
        return CONTINUE;

    return STOP;
}

BBoxRenderCase::IterationConfig BBoxRenderCase::generateRandomConfig(int seed, const tcu::IVec2 &renderTargetSize) const
{
    de::Random rnd(seed);
    IterationConfig config;

    // viewport config
    config.viewportSize.x() = rnd.getInt(MIN_VIEWPORT_SIZE, de::min<int>(renderTargetSize.x(), MAX_VIEWPORT_SIZE));
    config.viewportSize.y() = rnd.getInt(MIN_VIEWPORT_SIZE, de::min<int>(renderTargetSize.y(), MAX_VIEWPORT_SIZE));
    config.viewportPos.x()  = rnd.getInt(0, renderTargetSize.x() - config.viewportSize.x());
    config.viewportPos.y()  = rnd.getInt(0, renderTargetSize.y() - config.viewportSize.y());

    // pattern location inside viewport
    config.patternSize.x() = rnd.getFloat(0.4f, 1.4f);
    config.patternSize.y() = rnd.getFloat(0.4f, 1.4f);
    config.patternPos.x()  = rnd.getFloat(-1.0f, 1.0f - config.patternSize.x());
    config.patternPos.y()  = rnd.getFloat(-1.0f, 1.0f - config.patternSize.y());

    // accurate bounding box
    config.bbox.min = tcu::Vec4(config.patternPos.x(), config.patternPos.y(), 0.0f, 1.0f);
    config.bbox.max = tcu::Vec4(config.patternPos.x() + config.patternSize.x(),
                                config.patternPos.y() + config.patternSize.y(), 0.0f, 1.0f);

    if (m_bboxSize == BBOXSIZE_LARGER)
    {
        // increase bbox size
        config.bbox.min.x() -= rnd.getFloat() * 0.5f;
        config.bbox.min.y() -= rnd.getFloat() * 0.5f;
        config.bbox.min.z() -= rnd.getFloat() * 0.5f;

        config.bbox.max.x() += rnd.getFloat() * 0.5f;
        config.bbox.max.y() += rnd.getFloat() * 0.5f;
        config.bbox.max.z() += rnd.getFloat() * 0.5f;
    }
    else if (m_bboxSize == BBOXSIZE_SMALLER)
    {
        // reduce bbox size
        config.bbox.min.x() += rnd.getFloat() * 0.4f * config.patternSize.x();
        config.bbox.min.y() += rnd.getFloat() * 0.4f * config.patternSize.y();

        config.bbox.max.x() -= rnd.getFloat() * 0.4f * config.patternSize.x();
        config.bbox.max.y() -= rnd.getFloat() * 0.4f * config.patternSize.y();
    }

    return config;
}

tcu::IVec4 BBoxRenderCase::getViewportPatternArea(const tcu::Vec2 &patternPos, const tcu::Vec2 &patternSize,
                                                  const tcu::IVec2 &viewportSize, AABBRoundDirection roundDir) const
{
    const float halfPixel = 0.5f;
    tcu::Vec4 vertexBox;
    tcu::IVec4 pixelBox;

    vertexBox.x() = (patternPos.x() * 0.5f + 0.5f) * (float)viewportSize.x();
    vertexBox.y() = (patternPos.y() * 0.5f + 0.5f) * (float)viewportSize.y();
    vertexBox.z() = ((patternPos.x() + patternSize.x()) * 0.5f + 0.5f) * (float)viewportSize.x();
    vertexBox.w() = ((patternPos.y() + patternSize.y()) * 0.5f + 0.5f) * (float)viewportSize.y();

    if (roundDir == ROUND_INWARDS)
    {
        pixelBox.x() = (int)deFloatCeil(vertexBox.x() + halfPixel);
        pixelBox.y() = (int)deFloatCeil(vertexBox.y() + halfPixel);
        pixelBox.z() = (int)deFloatFloor(vertexBox.z() - halfPixel);
        pixelBox.w() = (int)deFloatFloor(vertexBox.w() - halfPixel);
    }
    else
    {
        pixelBox.x() = (int)deFloatFloor(vertexBox.x() - halfPixel);
        pixelBox.y() = (int)deFloatFloor(vertexBox.y() - halfPixel);
        pixelBox.z() = (int)deFloatCeil(vertexBox.z() + halfPixel);
        pixelBox.w() = (int)deFloatCeil(vertexBox.w() + halfPixel);
    }

    return pixelBox;
}

void BBoxRenderCase::setupRender(const IterationConfig &config)
{
    const glw::Functions &gl          = m_context.getRenderContext().getFunctions();
    const glw::GLint posLocation      = gl.getAttribLocation(m_program->getProgram(), "a_position");
    const glw::GLint colLocation      = gl.getAttribLocation(m_program->getProgram(), "a_color");
    const glw::GLint posScaleLocation = gl.getUniformLocation(m_program->getProgram(), "u_posScale");

    TCU_CHECK(posLocation != -1);
    TCU_CHECK(colLocation != -1);
    TCU_CHECK(posScaleLocation != -1);

    m_testCtx.getLog() << tcu::TestLog::Message << "Setting viewport to ("
                       << "x: " << config.viewportPos.x() << ", "
                       << "y: " << config.viewportPos.y() << ", "
                       << "w: " << config.viewportSize.x() << ", "
                       << "h: " << config.viewportSize.y() << ")\n"
                       << "Vertex coordinates are in range:\n"
                       << "\tx: [" << config.patternPos.x() << ", " << (config.patternPos.x() + config.patternSize.x())
                       << "]\n"
                       << "\ty: [" << config.patternPos.y() << ", " << (config.patternPos.y() + config.patternSize.y())
                       << "]\n"
                       << tcu::TestLog::EndMessage;

    if (!m_calcPerPrimitiveBBox)
        m_testCtx.getLog() << tcu::TestLog::Message << "Setting primitive bounding box to:\n"
                           << "\t" << config.bbox.min << "\n"
                           << "\t" << config.bbox.max << "\n"
                           << tcu::TestLog::EndMessage;

    if (m_useGlobalState)
        m_boundingBoxFunc(config.bbox.min.x(), config.bbox.min.y(), config.bbox.min.z(), config.bbox.min.w(),
                          config.bbox.max.x(), config.bbox.max.y(), config.bbox.max.z(), config.bbox.max.w());
    else
        // state is overriden by the tessellation output, set bbox to invisible area to imitiate dirty state left by application
        m_boundingBoxFunc(-2.0f, -2.0f, 0.0f, 1.0f, -1.7f, -1.7f, 0.0f, 1.0f);

    if (m_fbo)
        gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, **m_fbo);

    gl.viewport(config.viewportPos.x(), config.viewportPos.y(), config.viewportSize.x(), config.viewportSize.y());
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);

    gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);

    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, (int)(VA_NUM_ATTRIB_VECS * sizeof(float[4])),
                           glu::BufferOffsetAsPointer(4 * VA_POS_VEC_NDX * sizeof(float)));
    gl.vertexAttribPointer(colLocation, 4, GL_FLOAT, GL_FALSE, (int)(VA_NUM_ATTRIB_VECS * sizeof(float[4])),
                           glu::BufferOffsetAsPointer(4 * VA_COL_VEC_NDX * sizeof(float)));
    gl.enableVertexAttribArray(posLocation);
    gl.enableVertexAttribArray(colLocation);
    gl.useProgram(m_program->getProgram());
    gl.uniform4f(posScaleLocation, config.patternPos.x(), config.patternPos.y(), config.patternSize.x(),
                 config.patternSize.y());

    {
        const glw::GLint bboxMinPos = gl.getUniformLocation(m_program->getProgram(), "u_primitiveBBoxMin");
        const glw::GLint bboxMaxPos = gl.getUniformLocation(m_program->getProgram(), "u_primitiveBBoxMax");

        gl.uniform4f(bboxMinPos, config.bbox.min.x(), config.bbox.min.y(), config.bbox.min.z(), config.bbox.min.w());
        gl.uniform4f(bboxMaxPos, config.bbox.max.x(), config.bbox.max.y(), config.bbox.max.z(), config.bbox.max.w());
    }

    gl.uniform2i(gl.getUniformLocation(m_program->getProgram(), "u_viewportPos"), config.viewportPos.x(),
                 config.viewportPos.y());
    gl.uniform2i(gl.getUniformLocation(m_program->getProgram(), "u_viewportSize"), config.viewportSize.x(),
                 config.viewportSize.y());

    GLU_EXPECT_NO_ERROR(gl.getError(), "setup");
}

const char *BBoxRenderCase::genShaderFunction(ShaderFunction func) const
{
    switch (func)
    {
    case SHADER_FUNC_MIRROR_X:
        return "vec4 mirrorX(in highp vec4 p)\n"
               "{\n"
               "    highp vec2 patternOffset = u_posScale.xy;\n"
               "    highp vec2 patternScale = u_posScale.zw;\n"
               "    highp vec2 patternCenter = patternOffset + patternScale * 0.5;\n"
               "    return vec4(2.0 * patternCenter.x - p.x, p.y, p.z, p.w);\n"
               "}\n";

    case SHADER_FUNC_MIRROR_Y:
        return "vec4 mirrorY(in highp vec4 p)\n"
               "{\n"
               "    highp vec2 patternOffset = u_posScale.xy;\n"
               "    highp vec2 patternScale = u_posScale.zw;\n"
               "    highp vec2 patternCenter = patternOffset + patternScale * 0.5;\n"
               "    return vec4(p.x, 2.0 * patternCenter.y - p.y, p.z, p.w);\n"
               "}\n";

    case SHADER_FUNC_INSIDE_BBOX:
        return "uniform highp ivec2 u_viewportPos;\n"
               "uniform highp ivec2 u_viewportSize;\n"
               "flat in highp float v_bbox_expansionSize;\n"
               "flat in highp vec3 v_bbox_clipMin;\n"
               "flat in highp vec3 v_bbox_clipMax;\n"
               "\n"
               "bool fragmentInsideTheBBox(in highp float depth)\n"
               "{\n"
               "    highp vec4 wc = vec4(floor((v_bbox_clipMin.x * 0.5 + 0.5) * float(u_viewportSize.x) - "
               "v_bbox_expansionSize/2.0),\n"
               "                         floor((v_bbox_clipMin.y * 0.5 + 0.5) * float(u_viewportSize.y) - "
               "v_bbox_expansionSize/2.0),\n"
               "                         ceil((v_bbox_clipMax.x * 0.5 + 0.5) * float(u_viewportSize.x) + "
               "v_bbox_expansionSize/2.0),\n"
               "                         ceil((v_bbox_clipMax.y * 0.5 + 0.5) * float(u_viewportSize.y) + "
               "v_bbox_expansionSize/2.0));\n"
               "    if (gl_FragCoord.x < float(u_viewportPos.x) + wc.x || gl_FragCoord.x > float(u_viewportPos.x) + "
               "wc.z ||\n"
               "        gl_FragCoord.y < float(u_viewportPos.y) + wc.y || gl_FragCoord.y > float(u_viewportPos.y) + "
               "wc.w)\n"
               "        return false;\n"
               "    const highp float dEpsilon = 0.001;\n"
               "    if (depth*2.0-1.0 < v_bbox_clipMin.z - dEpsilon || depth*2.0-1.0 > v_bbox_clipMax.z + dEpsilon)\n"
               "        return false;\n"
               "    return true;\n"
               "}\n";
    default:
        DE_ASSERT(false);
        return "";
    }
}

class GridRenderCase : public BBoxRenderCase
{
public:
    GridRenderCase(Context &context, const char *name, const char *description, uint32_t flags);
    ~GridRenderCase(void);

private:
    void init(void);

    std::string genVertexSource(void) const;
    std::string genFragmentSource(void) const;
    std::string genTessellationControlSource(void) const;
    std::string genTessellationEvaluationSource(void) const;
    std::string genGeometrySource(void) const;

    IterationConfig generateConfig(int iteration, const tcu::IVec2 &renderTargetSize) const;
    void getAttributeData(std::vector<tcu::Vec4> &data) const;
    void renderTestPattern(const IterationConfig &config);
    void verifyRenderResult(const IterationConfig &config);

    const int m_gridSize;
};

GridRenderCase::GridRenderCase(Context &context, const char *name, const char *description, uint32_t flags)
    : BBoxRenderCase(context, name, description, 12, flags)
    , m_gridSize(24)
{
}

GridRenderCase::~GridRenderCase(void)
{
}

void GridRenderCase::init(void)
{
    m_testCtx.getLog()
        << tcu::TestLog::Message << "Rendering yellow-green grid to "
        << ((m_renderTarget == RENDERTARGET_DEFAULT) ? ("default frame buffer") : ("fbo")) << ".\n"
        << "Grid cells are in random order, varying grid size and location for each iteration.\n"
        << "Marking all discardable fragments (fragments outside the bounding box) with a fully saturated blue channel."
        << tcu::TestLog::EndMessage;

    BBoxRenderCase::init();
}

std::string GridRenderCase::genVertexSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in highp vec4 a_position;\n"
           "in highp vec4 a_color;\n"
           "out highp vec4 vtx_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "\n";
    if (!m_hasTessellationStage)
    {
        DE_ASSERT(m_useGlobalState);
        buf << "uniform highp vec4 u_primitiveBBoxMin;\n"
               "uniform highp vec4 u_primitiveBBoxMax;\n"
               "\n"
               "flat out highp float v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_expansionSize;\n"
               "flat out highp vec3 v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMin;\n"
               "flat out highp vec3 v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMax;\n"
               "\n";
    }

    buf << "void main()\n"
           "{\n"
           "    highp vec2 patternOffset = u_posScale.xy;\n"
           "    highp vec2 patternScale = u_posScale.zw;\n"
           "    gl_Position = vec4(a_position.xy * patternScale + patternOffset, a_position.z, a_position.w);\n"
           "    vtx_color = a_color;\n";

    if (!m_hasTessellationStage)
    {
        DE_ASSERT(m_useGlobalState);
        buf << "\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_expansionSize = 0.0;\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMin =\n"
               "        min(vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / "
               "u_primitiveBBoxMin.w,\n"
               "            vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / "
               "u_primitiveBBoxMax.w);\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMax =\n"
               "        min(vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / "
               "u_primitiveBBoxMin.w,\n"
               "            vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / "
               "u_primitiveBBoxMax.w);\n";
    }

    buf << "}\n";

    return buf.str();
}

std::string GridRenderCase::genFragmentSource(void) const
{
    const char *const colorInputName = (m_hasGeometryStage)     ? ("geo_color") :
                                       (m_hasTessellationStage) ? ("tess_color") :
                                                                  ("vtx_color");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in mediump vec4 "
        << colorInputName
        << ";\n"
           "layout(location = 0) out mediump vec4 o_color;\n"
        << genShaderFunction(SHADER_FUNC_INSIDE_BBOX)
        << "\n"
           "void main()\n"
           "{\n"
           "    mediump vec4 baseColor = "
        << colorInputName
        << ";\n"
           "    mediump float blueChannel;\n"
           "    if (fragmentInsideTheBBox(gl_FragCoord.z))\n"
           "        blueChannel = 0.0;\n"
           "    else\n"
           "        blueChannel = 1.0;\n"
           "    o_color = vec4(baseColor.r, baseColor.g, blueChannel, baseColor.a);\n"
           "}\n";

    return buf.str();
}

std::string GridRenderCase::genTessellationControlSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${ARB_ES32_COMPATIBILITY_REQUIRE}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${PRIMITIVE_BOUNDING_BOX_REQUIRE}\n"
           "layout(vertices=3) out;\n"
           "\n"
           "in highp vec4 vtx_color[];\n"
           "out highp vec4 tess_ctrl_color[];\n"
           "uniform highp float u_tessellationLevel;\n"
           "uniform highp vec4 u_posScale;\n";

    if (!m_calcPerPrimitiveBBox)
    {
        buf << "uniform highp vec4 u_primitiveBBoxMin;\n"
               "uniform highp vec4 u_primitiveBBoxMax;\n";
    }

    buf << "patch out highp float vp_bbox_expansionSize;\n"
           "patch out highp vec3 vp_bbox_clipMin;\n"
           "patch out highp vec3 vp_bbox_clipMax;\n";

    if (m_calcPerPrimitiveBBox)
    {
        buf << "\n";
        if (m_hasGeometryStage)
            buf << genShaderFunction(SHADER_FUNC_MIRROR_X);
        buf << genShaderFunction(SHADER_FUNC_MIRROR_Y);

        buf << "vec4 transformVec(in highp vec4 p)\n"
               "{\n"
               "    return "
            << ((m_hasGeometryStage) ? ("mirrorX(mirrorY(p))") : ("mirrorY(p)"))
            << ";\n"
               "}\n";
    }

    buf << "\n"
           "void main()\n"
           "{\n"
           "    // convert to nonsensical coordinates, just in case\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position.wzxy;\n"
           "    tess_ctrl_color[gl_InvocationID] = vtx_color[gl_InvocationID];\n"
           "\n"
           "    gl_TessLevelOuter[0] = u_tessellationLevel;\n"
           "    gl_TessLevelOuter[1] = u_tessellationLevel;\n"
           "    gl_TessLevelOuter[2] = u_tessellationLevel;\n"
           "    gl_TessLevelInner[0] = u_tessellationLevel;\n";

    if (m_calcPerPrimitiveBBox)
    {
        buf << "\n"
               "    highp vec4 bboxMin = min(min(transformVec(gl_in[0].gl_Position),\n"
               "                                 transformVec(gl_in[1].gl_Position)),\n"
               "                             transformVec(gl_in[2].gl_Position));\n"
               "    highp vec4 bboxMax = max(max(transformVec(gl_in[0].gl_Position),\n"
               "                                 transformVec(gl_in[1].gl_Position)),\n"
               "                             transformVec(gl_in[2].gl_Position));\n";
    }
    else
    {
        buf << "\n"
               "    highp vec4 bboxMin = u_primitiveBBoxMin;\n"
               "    highp vec4 bboxMax = u_primitiveBBoxMax;\n";
    }

    if (!m_useGlobalState)
        buf << "\n"
               "    ${PRIM_GL_BOUNDING_BOX}[0] = bboxMin;\n"
               "    ${PRIM_GL_BOUNDING_BOX}[1] = bboxMax;\n";

    buf << "    vp_bbox_expansionSize = 0.0;\n"
           "    vp_bbox_clipMin = min(vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMin.w,\n"
           "                          vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMax.w);\n"
           "    vp_bbox_clipMax = max(vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMin.w,\n"
           "                          vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMax.w);\n"
           "}\n";

    return buf.str();
}

std::string GridRenderCase::genTessellationEvaluationSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${GPU_SHADER5_REQUIRE}\n"
           "layout(triangles) in;\n"
           "\n"
           "in highp vec4 tess_ctrl_color[];\n"
           "out highp vec4 tess_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "patch in highp float vp_bbox_expansionSize;\n"
           "patch in highp vec3 vp_bbox_clipMin;\n"
           "patch in highp vec3 vp_bbox_clipMax;\n"
           "flat out highp float v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_expansionSize;\n"
           "flat out highp vec3 v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMin;\n"
           "flat out highp vec3 v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMax;\n"
           "\n"
           "precise gl_Position;\n"
           "\n"
        << genShaderFunction(SHADER_FUNC_MIRROR_Y)
        << "void main()\n"
           "{\n"
           "    // non-trivial tessellation evaluation shader, convert from nonsensical coords, flip vertically\n"
           "    gl_Position = mirrorY(gl_TessCoord.x * gl_in[0].gl_Position.zwyx +\n"
           "                          gl_TessCoord.y * gl_in[1].gl_Position.zwyx +\n"
           "                          gl_TessCoord.z * gl_in[2].gl_Position.zwyx);\n"
           "    tess_color = tess_ctrl_color[0];\n"
           "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_expansionSize = vp_bbox_expansionSize;\n"
           "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMin = vp_bbox_clipMin;\n"
           "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMax = vp_bbox_clipMax;\n"
           "}\n";

    return buf.str();
}

std::string GridRenderCase::genGeometrySource(void) const
{
    const char *const colorInputName = (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GEOMETRY_SHADER_REQUIRE}\n"
           "layout(triangles) in;\n"
           "layout(max_vertices=9, triangle_strip) out;\n"
           "\n"
           "in highp vec4 "
        << colorInputName
        << "[3];\n"
           "out highp vec4 geo_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "\n"
           "flat in highp float v_geo_bbox_expansionSize[3];\n"
           "flat in highp vec3 v_geo_bbox_clipMin[3];\n"
           "flat in highp vec3 v_geo_bbox_clipMax[3];\n"
           "flat out highp vec3 v_bbox_clipMin;\n"
           "flat out highp vec3 v_bbox_clipMax;\n"
           "flat out highp float v_bbox_expansionSize;\n"
        << genShaderFunction(SHADER_FUNC_MIRROR_X)
        << "\n"
           "void setVisualizationVaryings()\n"
           "{\n"
           "    v_bbox_expansionSize = v_geo_bbox_expansionSize[0];\n"
           "    v_bbox_clipMin = v_geo_bbox_clipMin[0];\n"
           "    v_bbox_clipMax = v_geo_bbox_clipMax[0];\n"
           "}\n"
           "void main()\n"
           "{\n"
           "    // Non-trivial geometry shader: 1-to-3 amplification, mirror horizontally\n"
           "    highp vec4 p0 = mirrorX(gl_in[0].gl_Position);\n"
           "    highp vec4 p1 = mirrorX(gl_in[1].gl_Position);\n"
           "    highp vec4 p2 = mirrorX(gl_in[2].gl_Position);\n"
           "    highp vec4 pCentroid = vec4((p0.xyz + p1.xyz + p2.xyz) / 3.0, 1.0);\n"
           "    highp vec4 triangleColor = "
        << colorInputName
        << "[0];\n"
           "\n"
           "    gl_Position = p0; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = p1; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = pCentroid; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    EndPrimitive();\n"
           "\n"
           "    gl_Position = p1; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = p2; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = pCentroid; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    EndPrimitive();\n"
           "\n"
           "    gl_Position = p2; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = p0; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = pCentroid; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
           "    EndPrimitive();\n"
           "}\n";

    return buf.str();
}

GridRenderCase::IterationConfig GridRenderCase::generateConfig(int iteration, const tcu::IVec2 &renderTargetSize) const
{
    return generateRandomConfig(0xDEDEDEu * (uint32_t)iteration, renderTargetSize);
}

void GridRenderCase::getAttributeData(std::vector<tcu::Vec4> &data) const
{
    const tcu::Vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
    const tcu::Vec4 yellow(1.0f, 1.0f, 0.0f, 1.0f);
    std::vector<int> cellOrder(m_gridSize * m_gridSize);
    de::Random rnd(0xDE56789);

    // generate grid with cells in random order
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
        cellOrder[ndx] = ndx;
    rnd.shuffle(cellOrder.begin(), cellOrder.end());

    data.resize(m_gridSize * m_gridSize * 6 * 2);
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
    {
        const int cellNdx          = cellOrder[ndx];
        const int cellX            = cellNdx % m_gridSize;
        const int cellY            = cellNdx / m_gridSize;
        const tcu::Vec4 &cellColor = ((cellX + cellY) % 2 == 0) ? (green) : (yellow);

        data[(ndx * 6 + 0) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
            tcu::Vec4(float(cellX + 0) / float(m_gridSize), float(cellY + 0) / float(m_gridSize), 0.0f, 1.0f);
        data[(ndx * 6 + 0) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
        data[(ndx * 6 + 1) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
            tcu::Vec4(float(cellX + 1) / float(m_gridSize), float(cellY + 1) / float(m_gridSize), 0.0f, 1.0f);
        data[(ndx * 6 + 1) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
        data[(ndx * 6 + 2) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
            tcu::Vec4(float(cellX + 0) / float(m_gridSize), float(cellY + 1) / float(m_gridSize), 0.0f, 1.0f);
        data[(ndx * 6 + 2) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
        data[(ndx * 6 + 3) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
            tcu::Vec4(float(cellX + 0) / float(m_gridSize), float(cellY + 0) / float(m_gridSize), 0.0f, 1.0f);
        data[(ndx * 6 + 3) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
        data[(ndx * 6 + 4) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
            tcu::Vec4(float(cellX + 1) / float(m_gridSize), float(cellY + 0) / float(m_gridSize), 0.0f, 1.0f);
        data[(ndx * 6 + 4) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
        data[(ndx * 6 + 5) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
            tcu::Vec4(float(cellX + 1) / float(m_gridSize), float(cellY + 1) / float(m_gridSize), 0.0f, 1.0f);
        data[(ndx * 6 + 5) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
    }
}

void GridRenderCase::renderTestPattern(const IterationConfig &config)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    setupRender(config);

    if (m_hasTessellationStage)
    {
        const glw::GLint tessLevelPos = gl.getUniformLocation(m_program->getProgram(), "u_tessellationLevel");
        const glw::GLfloat tessLevel  = 2.8f; // will be rounded up

        TCU_CHECK(tessLevelPos != -1);

        m_testCtx.getLog() << tcu::TestLog::Message << "u_tessellationLevel = " << tessLevel
                           << tcu::TestLog::EndMessage;

        gl.uniform1f(tessLevelPos, tessLevel);
        gl.patchParameteri(GL_PATCH_VERTICES, 3);
        GLU_EXPECT_NO_ERROR(gl.getError(), "patch param");
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering grid." << tcu::TestLog::EndMessage;

    gl.drawArrays((m_hasTessellationStage) ? (GL_PATCHES) : (GL_TRIANGLES), 0, m_gridSize * m_gridSize * 6);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

void GridRenderCase::verifyRenderResult(const IterationConfig &config)
{
    const glw::Functions &gl          = m_context.getRenderContext().getFunctions();
    const ProjectedBBox projectedBBox = projectBoundingBox(config.bbox);
    const tcu::IVec4 viewportBBoxArea = getViewportBoundingBoxArea(projectedBBox, config.viewportSize);
    const tcu::IVec4 viewportGridOuterArea =
        getViewportPatternArea(config.patternPos, config.patternSize, config.viewportSize, ROUND_OUTWARDS);
    const tcu::IVec4 viewportGridInnerArea =
        getViewportPatternArea(config.patternPos, config.patternSize, config.viewportSize, ROUND_INWARDS);
    tcu::Surface viewportSurface(config.viewportSize.x(), config.viewportSize.y());
    tcu::Surface errorMask(config.viewportSize.x(), config.viewportSize.y());
    bool anyError = false;

    if (!m_calcPerPrimitiveBBox)
        m_testCtx.getLog() << tcu::TestLog::Message << "Projected bounding box: (clip space)\n"
                           << "\tx: [" << projectedBBox.min.x() << "," << projectedBBox.max.x() << "]\n"
                           << "\ty: [" << projectedBBox.min.y() << "," << projectedBBox.max.y() << "]\n"
                           << "\tz: [" << projectedBBox.min.z() << "," << projectedBBox.max.z() << "]\n"
                           << "In viewport coordinates:\n"
                           << "\tx: [" << viewportBBoxArea.x() << ", " << viewportBBoxArea.z() << "]\n"
                           << "\ty: [" << viewportBBoxArea.y() << ", " << viewportBBoxArea.w() << "]\n"
                           << "Verifying render results within the bounding box.\n"
                           << tcu::TestLog::EndMessage;
    else
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying render result." << tcu::TestLog::EndMessage;

    if (m_fbo)
        gl.bindFramebuffer(GL_READ_FRAMEBUFFER, **m_fbo);
    glu::readPixels(m_context.getRenderContext(), config.viewportPos.x(), config.viewportPos.y(),
                    viewportSurface.getAccess());

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

    for (int y = de::max(viewportBBoxArea.y(), 0); y < de::min(viewportBBoxArea.w(), config.viewportSize.y()); ++y)
        for (int x = de::max(viewportBBoxArea.x(), 0); x < de::min(viewportBBoxArea.z(), config.viewportSize.x()); ++x)
        {
            const tcu::RGBA pixel  = viewportSurface.getPixel(x, y);
            const bool outsideGrid = x < viewportGridOuterArea.x() || y < viewportGridOuterArea.y() ||
                                     x > viewportGridOuterArea.z() || y > viewportGridOuterArea.w();
            const bool insideGrid = x > viewportGridInnerArea.x() && y > viewportGridInnerArea.y() &&
                                    x < viewportGridInnerArea.z() && y < viewportGridInnerArea.w();

            bool error = false;

            if (outsideGrid)
            {
                // expect black
                if (pixel.getRed() != 0 || pixel.getGreen() != 0 || pixel.getBlue() != 0)
                    error = true;
            }

            else if (insideGrid)
            {
                // expect green, yellow or a combination of these
                if (pixel.getGreen() != 255 || pixel.getBlue() != 0)
                    error = true;
            }
            else
            {
                // boundary, allow anything
            }

            if (error)
            {
                errorMask.setPixel(x, y, tcu::RGBA::red());
                anyError = true;
            }
        }

    if (anyError)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
                           << tcu::TestLog::EndImageSet;

        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Result image ok." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
                           << tcu::TestLog::EndImageSet;
    }
}

class LineRenderCase : public BBoxRenderCase
{
public:
    enum
    {
        LINEFLAG_WIDE = 1u << FLAGBIT_USER_BIT, //!< use wide lines
    };

    LineRenderCase(Context &context, const char *name, const char *description, uint32_t flags);
    ~LineRenderCase(void);

private:
    enum
    {
        GREEN_COMPONENT_NDX = 1,
        BLUE_COMPONENT_NDX  = 2,

        SCAN_ROW_COMPONENT_NDX = GREEN_COMPONENT_NDX, // \note: scans are orthogonal to the line
        SCAN_COL_COMPONENT_NDX = BLUE_COMPONENT_NDX,
    };

    enum QueryDirection
    {
        DIRECTION_HORIZONTAL = 0,
        DIRECTION_VERTICAL,
    };

    enum ScanResult
    {
        SCANRESULT_NUM_LINES_OK_BIT    = (1 << 0),
        SCANRESULT_LINE_WIDTH_OK_BIT   = (1 << 1),
        SCANRESULT_LINE_WIDTH_WARN_BIT = (1 << 2),
        SCANRESULT_LINE_WIDTH_ERR_BIT  = (1 << 3),
        SCANRESULT_LINE_CONT_OK_BIT    = (1 << 4),
        SCANRESULT_LINE_CONT_ERR_BIT   = (1 << 5),
        SCANRESULT_LINE_CONT_WARN_BIT  = (1 << 6),
    };

    void init(void);

    std::string genVertexSource(void) const;
    std::string genFragmentSource(void) const;
    std::string genTessellationControlSource(void) const;
    std::string genTessellationEvaluationSource(void) const;
    std::string genGeometrySource(void) const;

    IterationConfig generateConfig(int iteration, const tcu::IVec2 &renderTargetSize) const;
    void getAttributeData(std::vector<tcu::Vec4> &data) const;
    void renderTestPattern(const IterationConfig &config);
    void verifyRenderResult(const IterationConfig &config);

    tcu::IVec2 getNumberOfLinesRange(int queryAreaBegin, int queryAreaEnd, float patternStart, float patternSize,
                                     int viewportArea, QueryDirection queryDir) const;
    uint8_t scanRow(const tcu::ConstPixelBufferAccess &access, int row, int rowBegin, int rowEnd, int rowViewportBegin,
                    int rowViewportEnd, const tcu::IVec2 &numLines, int &floodCounter) const;
    uint8_t scanColumn(const tcu::ConstPixelBufferAccess &access, int column, int columnBegin, int columnEnd,
                       int columnViewportBegin, int columnViewportEnd, const tcu::IVec2 &numLines,
                       int &floodCounter) const;
    bool checkAreaNumLines(const tcu::ConstPixelBufferAccess &access, const tcu::IVec4 &area, int &floodCounter,
                           int componentNdx, const tcu::IVec2 &numLines) const;
    uint8_t checkLineContinuity(const tcu::ConstPixelBufferAccess &access, const tcu::IVec2 &begin,
                                const tcu::IVec2 &end, int componentNdx, int &messageLimitCounter) const;
    tcu::IVec2 getNumMinimaMaxima(const tcu::ConstPixelBufferAccess &access, int componentNdx) const;
    uint8_t checkLineWidths(const tcu::ConstPixelBufferAccess &access, const tcu::IVec2 &begin, const tcu::IVec2 &end,
                            int componentNdx, int &floodCounter) const;
    void printLineWidthError(const tcu::IVec2 &pos, int detectedLineWidth, const tcu::IVec2 &lineWidthRange,
                             bool isHorizontal, int &floodCounter) const;

    const int m_patternSide;
    const bool m_isWideLineCase;
    const int m_wideLineLineWidth;
};

LineRenderCase::LineRenderCase(Context &context, const char *name, const char *description, uint32_t flags)
    : BBoxRenderCase(context, name, description, 12, flags)
    , m_patternSide(12)
    , m_isWideLineCase((flags & LINEFLAG_WIDE) != 0)
    , m_wideLineLineWidth(5)
{
}

LineRenderCase::~LineRenderCase(void)
{
}

void LineRenderCase::init(void)
{
    m_testCtx.getLog()
        << tcu::TestLog::Message << "Rendering line pattern to "
        << ((m_renderTarget == RENDERTARGET_DEFAULT) ? ("default frame buffer") : ("fbo")) << ".\n"
        << "Vertical lines are green, horizontal lines blue. Using additive blending.\n"
        << "Line segments are in random order, varying pattern size and location for each iteration.\n"
        << "Marking all discardable fragments (fragments outside the bounding box) with a fully saturated red channel."
        << tcu::TestLog::EndMessage;

    if (m_isWideLineCase)
    {
        glw::GLfloat lineWidthRange[2] = {0.0f, 0.0f};
        m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);

        if (lineWidthRange[1] < (float)m_wideLineLineWidth)
            throw tcu::NotSupportedError("Test requires line width " + de::toString(m_wideLineLineWidth));
    }

    BBoxRenderCase::init();
}

std::string LineRenderCase::genVertexSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in highp vec4 a_position;\n"
           "in highp vec4 a_color;\n"
           "out highp vec4 vtx_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "uniform highp float u_lineWidth;\n"
           "\n";
    if (!m_hasTessellationStage)
    {
        DE_ASSERT(m_useGlobalState);
        buf << "uniform highp vec4 u_primitiveBBoxMin;\n"
               "uniform highp vec4 u_primitiveBBoxMax;\n"
               "\n"
               "flat out highp float v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_expansionSize;\n"
               "flat out highp vec3 v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMin;\n"
               "flat out highp vec3 v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMax;\n"
               "\n";
    }
    buf << "void main()\n"
           "{\n"
           "    highp vec2 patternOffset = u_posScale.xy;\n"
           "    highp vec2 patternScale = u_posScale.zw;\n"
           "    gl_Position = vec4(a_position.xy * patternScale + patternOffset, a_position.z, a_position.w);\n"
           "    vtx_color = a_color;\n";
    if (!m_hasTessellationStage)
    {
        DE_ASSERT(m_useGlobalState);
        buf << "\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_expansionSize = u_lineWidth;\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMin =\n"
               "        min(vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / "
               "u_primitiveBBoxMin.w,\n"
               "            vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / "
               "u_primitiveBBoxMax.w);\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMax =\n"
               "        min(vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / "
               "u_primitiveBBoxMin.w,\n"
               "            vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / "
               "u_primitiveBBoxMax.w);\n";
    }
    buf << "}\n";

    return buf.str();
}

std::string LineRenderCase::genFragmentSource(void) const
{
    const char *const colorInputName = (m_hasGeometryStage)     ? ("geo_color") :
                                       (m_hasTessellationStage) ? ("tess_color") :
                                                                  ("vtx_color");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in mediump vec4 "
        << colorInputName
        << ";\n"
           "layout(location = 0) out mediump vec4 o_color;\n"
        << genShaderFunction(SHADER_FUNC_INSIDE_BBOX)
        << "\n"
           "void main()\n"
           "{\n"
           "    mediump vec4 baseColor = "
        << colorInputName
        << ";\n"
           "    mediump float redChannel;\n"
           "    if (fragmentInsideTheBBox(gl_FragCoord.z))\n"
           "        redChannel = 0.0;\n"
           "    else\n"
           "        redChannel = 1.0;\n"
           "    o_color = vec4(redChannel, baseColor.g, baseColor.b, baseColor.a);\n"
           "}\n";

    return buf.str();
}

std::string LineRenderCase::genTessellationControlSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${ARB_ES32_COMPATIBILITY_REQUIRE}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${PRIMITIVE_BOUNDING_BOX_REQUIRE}\n"
           "layout(vertices=2) out;"
           "\n"
           "in highp vec4 vtx_color[];\n"
           "out highp vec4 tess_ctrl_color[];\n"
           "uniform highp float u_tessellationLevel;\n"
           "uniform highp vec4 u_posScale;\n"
           "uniform highp float u_lineWidth;\n";

    if (!m_calcPerPrimitiveBBox)
    {
        buf << "uniform highp vec4 u_primitiveBBoxMin;\n"
               "uniform highp vec4 u_primitiveBBoxMax;\n";
    }

    buf << "patch out highp float vp_bbox_expansionSize;\n"
           "patch out highp vec3 vp_bbox_clipMin;\n"
           "patch out highp vec3 vp_bbox_clipMax;\n";

    if (m_calcPerPrimitiveBBox)
    {
        buf << "\n";
        if (m_hasGeometryStage)
            buf << genShaderFunction(SHADER_FUNC_MIRROR_X);
        buf << genShaderFunction(SHADER_FUNC_MIRROR_Y);

        buf << "vec4 transformVec(in highp vec4 p)\n"
               "{\n"
               "    return "
            << ((m_hasGeometryStage) ? ("mirrorX(mirrorY(p))") : ("mirrorY(p)"))
            << ";\n"
               "}\n";
    }

    buf << "\n"
           "void main()\n"
           "{\n"
           "    // convert to nonsensical coordinates, just in case\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position.wzxy;\n"
           "    tess_ctrl_color[gl_InvocationID] = vtx_color[gl_InvocationID];\n"
           "\n"
           "    gl_TessLevelOuter[0] = 0.8; // will be rounded up to 1\n"
           "    gl_TessLevelOuter[1] = u_tessellationLevel;\n";

    if (m_calcPerPrimitiveBBox)
    {
        buf << "\n"
               "    highp vec4 bboxMin = min(transformVec(gl_in[0].gl_Position),\n"
               "                             transformVec(gl_in[1].gl_Position));\n"
               "    highp vec4 bboxMax = max(transformVec(gl_in[0].gl_Position),\n"
               "                             transformVec(gl_in[1].gl_Position));\n";
    }
    else
    {
        buf << "\n"
               "    highp vec4 bboxMin = u_primitiveBBoxMin;\n"
               "    highp vec4 bboxMax = u_primitiveBBoxMax;\n";
    }

    if (!m_useGlobalState)
        buf << "\n"
               "    ${PRIM_GL_BOUNDING_BOX}[0] = bboxMin;\n"
               "    ${PRIM_GL_BOUNDING_BOX}[1] = bboxMax;\n";

    buf << "    vp_bbox_expansionSize = u_lineWidth;\n"
           "    vp_bbox_clipMin = min(vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMin.w,\n"
           "                          vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMax.w);\n"
           "    vp_bbox_clipMax = max(vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMin.w,\n"
           "                          vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMax.w);\n"
           "}\n";

    return buf.str();
}

std::string LineRenderCase::genTessellationEvaluationSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "layout(isolines) in;"
           "\n"
           "in highp vec4 tess_ctrl_color[];\n"
           "out highp vec4 tess_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "\n"
           "patch in highp float vp_bbox_expansionSize;\n"
           "patch in highp vec3 vp_bbox_clipMin;\n"
           "patch in highp vec3 vp_bbox_clipMax;\n"
           "flat out highp float v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_expansionSize;\n"
           "flat out highp vec3 v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMin;\n"
           "flat out highp vec3 v_"
        << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax;\n"
        << genShaderFunction(SHADER_FUNC_MIRROR_Y)
        << "void main()\n"
           "{\n"
           "    // non-trivial tessellation evaluation shader, convert from nonsensical coords, flip vertically\n"
           "    gl_Position = mirrorY(mix(gl_in[0].gl_Position.zwyx, gl_in[1].gl_Position.zwyx, gl_TessCoord.x));\n"
           "    tess_color = tess_ctrl_color[0];\n"
           "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_expansionSize = vp_bbox_expansionSize;\n"
           "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMin = vp_bbox_clipMin;\n"
           "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMax = vp_bbox_clipMax;\n"
           "}\n";

    return buf.str();
}

std::string LineRenderCase::genGeometrySource(void) const
{
    const char *const colorInputName = (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GEOMETRY_SHADER_REQUIRE}\n"
           "layout(lines) in;\n"
           "layout(max_vertices=5, line_strip) out;\n"
           "\n"
           "in highp vec4 "
        << colorInputName
        << "[2];\n"
           "out highp vec4 geo_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "\n"
           "\n"
           "flat in highp float v_geo_bbox_expansionSize[2];\n"
           "flat in highp vec3 v_geo_bbox_clipMin[2];\n"
           "flat in highp vec3 v_geo_bbox_clipMax[2];\n"
           "flat out highp vec3 v_bbox_clipMin;\n"
           "flat out highp vec3 v_bbox_clipMax;\n"
           "flat out highp float v_bbox_expansionSize;\n"
        << genShaderFunction(SHADER_FUNC_MIRROR_X)
        << "\n"
           "void setVisualizationVaryings()\n"
           "{\n"
           "    v_bbox_expansionSize = v_geo_bbox_expansionSize[0];\n"
           "    v_bbox_clipMin = v_geo_bbox_clipMin[0];\n"
           "    v_bbox_clipMax = v_geo_bbox_clipMax[0];\n"
           "}\n"
           "void main()\n"
           "{\n"
           "    // Non-trivial geometry shader: 1-to-3 amplification, mirror horizontally\n"
           "    highp vec4 p0 = mirrorX(gl_in[0].gl_Position);\n"
           "    highp vec4 p1 = mirrorX(gl_in[1].gl_Position);\n"
           "    highp vec4 lineColor = "
        << colorInputName
        << "[0];\n"
           "\n"
           "    // output two separate primitives, just in case\n"
           "    gl_Position = mix(p0, p1, 0.00); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = mix(p0, p1, 0.33); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
           "    EndPrimitive();\n"
           "\n"
           "    gl_Position = mix(p0, p1, 0.33); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = mix(p0, p1, 0.67); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
           "    gl_Position = mix(p0, p1, 1.00); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
           "    EndPrimitive();\n"
           "}\n";

    return buf.str();
}

LineRenderCase::IterationConfig LineRenderCase::generateConfig(int iteration, const tcu::IVec2 &renderTargetSize) const
{
    const int numMaxAttempts = 128;

    // Avoid too narrow viewports, lines could merge together. Require viewport is at least 2.5x the size of the line bodies.
    for (int attemptNdx = 0; attemptNdx < numMaxAttempts; ++attemptNdx)
    {
        const IterationConfig &config =
            generateRandomConfig((0xDEDEDEu * (uint32_t)iteration) ^ (0xABAB13 * attemptNdx), renderTargetSize);

        if ((float)config.viewportSize.x() * (config.patternSize.x() * 0.5f) >
                2.5f * (float)m_patternSide * (float)m_wideLineLineWidth &&
            (float)config.viewportSize.y() * (config.patternSize.y() * 0.5f) >
                2.5f * (float)m_patternSide * (float)m_wideLineLineWidth)
        {
            return config;
        }
    }

    DE_ASSERT(false);
    return IterationConfig();
}

void LineRenderCase::getAttributeData(std::vector<tcu::Vec4> &data) const
{
    const tcu::Vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
    const tcu::Vec4 blue(0.0f, 0.0f, 1.0f, 1.0f);
    std::vector<int> cellOrder(m_patternSide * m_patternSide * 2);
    de::Random rnd(0xDE12345);

    // generate crosshatch pattern with segments in random order
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
        cellOrder[ndx] = ndx;
    rnd.shuffle(cellOrder.begin(), cellOrder.end());

    data.resize(cellOrder.size() * 4);
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
    {
        const int segmentID  = cellOrder[ndx];
        const int direction  = segmentID & 0x01;
        const int majorCoord = (segmentID >> 1) / m_patternSide;
        const int minorCoord = (segmentID >> 1) % m_patternSide;

        if (direction)
        {
            data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(
                float(minorCoord) / float(m_patternSide), float(majorCoord) / float(m_patternSide), 0.0f, 1.0f);
            data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = green;
            data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(
                float(minorCoord) / float(m_patternSide), float(majorCoord + 1) / float(m_patternSide), 0.0f, 1.0f);
            data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = green;
        }
        else
        {
            data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(
                float(majorCoord) / float(m_patternSide), float(minorCoord) / float(m_patternSide), 0.0f, 1.0f);
            data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = blue;
            data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(
                float(majorCoord + 1) / float(m_patternSide), float(minorCoord) / float(m_patternSide), 0.0f, 1.0f);
            data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = blue;
        }
    }
}

void LineRenderCase::renderTestPattern(const IterationConfig &config)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    setupRender(config);

    if (m_hasTessellationStage)
    {
        const glw::GLint tessLevelPos = gl.getUniformLocation(m_program->getProgram(), "u_tessellationLevel");
        const glw::GLfloat tessLevel  = 2.8f; // will be rounded up

        TCU_CHECK(tessLevelPos != -1);

        m_testCtx.getLog() << tcu::TestLog::Message << "u_tessellationLevel = " << tessLevel
                           << tcu::TestLog::EndMessage;

        gl.uniform1f(tessLevelPos, tessLevel);
        gl.patchParameteri(GL_PATCH_VERTICES, 2);
        GLU_EXPECT_NO_ERROR(gl.getError(), "patch param");
    }

    if (m_isWideLineCase)
        gl.lineWidth((float)m_wideLineLineWidth);

    gl.uniform1f(gl.getUniformLocation(m_program->getProgram(), "u_lineWidth"),
                 (m_isWideLineCase) ? ((float)m_wideLineLineWidth) : (1.0f));

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering pattern." << tcu::TestLog::EndMessage;

    gl.enable(GL_BLEND);
    gl.blendFunc(GL_ONE, GL_ONE);
    gl.blendEquation(GL_FUNC_ADD);

    gl.drawArrays((m_hasTessellationStage) ? (GL_PATCHES) : (GL_LINES), 0, m_patternSide * m_patternSide * 2 * 2);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

void LineRenderCase::verifyRenderResult(const IterationConfig &config)
{
    const glw::Functions &gl          = m_context.getRenderContext().getFunctions();
    const bool isMsaa                 = m_context.getRenderTarget().getNumSamples() > 1;
    const ProjectedBBox projectedBBox = projectBoundingBox(config.bbox);
    const float lineWidth             = (m_isWideLineCase) ? ((float)m_wideLineLineWidth) : (1.0f);
    const tcu::IVec4 viewportBBoxArea = getViewportBoundingBoxArea(projectedBBox, config.viewportSize, lineWidth);
    const tcu::IVec4 viewportPatternArea =
        getViewportPatternArea(config.patternPos, config.patternSize, config.viewportSize, ROUND_INWARDS);
    const tcu::IVec2 expectedHorizontalLines =
        getNumberOfLinesRange(viewportBBoxArea.y(), viewportBBoxArea.w(), config.patternPos.y(), config.patternSize.y(),
                              config.viewportSize.y(), DIRECTION_VERTICAL);
    const tcu::IVec2 expectedVerticalLines =
        getNumberOfLinesRange(viewportBBoxArea.x(), viewportBBoxArea.z(), config.patternPos.x(), config.patternSize.x(),
                              config.viewportSize.x(), DIRECTION_HORIZONTAL);
    const tcu::IVec4 verificationArea = tcu::IVec4(de::max(viewportBBoxArea.x(), 0), de::max(viewportBBoxArea.y(), 0),
                                                   de::min(viewportBBoxArea.z(), config.viewportSize.x()),
                                                   de::min(viewportBBoxArea.w(), config.viewportSize.y()));

    tcu::Surface viewportSurface(config.viewportSize.x(), config.viewportSize.y());
    int messageLimitCounter = 8;

    enum ScanResultCodes
    {
        SCANRESULT_NUM_LINES_ERR   = 0,
        SCANRESULT_LINE_WIDTH_MSAA = 1,
        SCANRESULT_LINE_WIDTH_WARN = 2,
        SCANRESULT_LINE_WIDTH_ERR  = 3,
        SCANRESULT_LINE_CONT_ERR   = 4,
        SCANRESULT_LINE_CONT_WARN  = 5,
        SCANRESULT_LINE_LAST
    };

    int rowScanResult[SCANRESULT_LINE_LAST]    = {0, 0, 0, 0, 0, 0};
    int columnScanResult[SCANRESULT_LINE_LAST] = {0, 0, 0, 0, 0, 0};
    bool anyError                              = false;
    bool msaaRelaxationRequired                = false;
    bool hwIssueRelaxationRequired             = false;

    if (!m_calcPerPrimitiveBBox)
        m_testCtx.getLog() << tcu::TestLog::Message << "Projected bounding box: (clip space)\n"
                           << "\tx: [" << projectedBBox.min.x() << "," << projectedBBox.max.x() << "]\n"
                           << "\ty: [" << projectedBBox.min.y() << "," << projectedBBox.max.y() << "]\n"
                           << "\tz: [" << projectedBBox.min.z() << "," << projectedBBox.max.z() << "]\n"
                           << "In viewport coordinates:\n"
                           << "\tx: [" << viewportBBoxArea.x() << ", " << viewportBBoxArea.z() << "]\n"
                           << "\ty: [" << viewportBBoxArea.y() << ", " << viewportBBoxArea.w() << "]\n"
                           << "Verifying render results within the bounding box:\n"
                           << tcu::TestLog::EndMessage;
    else
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying render result:" << tcu::TestLog::EndMessage;

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "\tCalculating number of horizontal and vertical lines within the bounding box, expecting:\n"
                       << "\t[" << expectedHorizontalLines.x() << ", " << expectedHorizontalLines.y()
                       << "] horizontal lines.\n"
                       << "\t[" << expectedVerticalLines.x() << ", " << expectedVerticalLines.y()
                       << "] vertical lines.\n"
                       << tcu::TestLog::EndMessage;

    if (m_fbo)
        gl.bindFramebuffer(GL_READ_FRAMEBUFFER, **m_fbo);
    glu::readPixels(m_context.getRenderContext(), config.viewportPos.x(), config.viewportPos.y(),
                    viewportSurface.getAccess());

    // scan rows
    for (int y = de::max(verificationArea.y(), viewportPatternArea.y());
         y < de::min(verificationArea.w(), viewportPatternArea.w()); ++y)
    {
        const uint8_t result =
            scanRow(viewportSurface.getAccess(), y, verificationArea.x(), verificationArea.z(),
                    de::max(verificationArea.x(), viewportPatternArea.x()),
                    de::min(verificationArea.z(), viewportPatternArea.z()), expectedVerticalLines, messageLimitCounter);

        if ((result & SCANRESULT_NUM_LINES_OK_BIT) == 0)
            rowScanResult[SCANRESULT_NUM_LINES_ERR]++;
        if ((result & SCANRESULT_LINE_CONT_OK_BIT) == 0)
        {
            if ((result & SCANRESULT_LINE_CONT_WARN_BIT) != 0)
                rowScanResult[SCANRESULT_LINE_CONT_WARN]++;
            else
                rowScanResult[SCANRESULT_LINE_CONT_ERR]++;
        }
        else if ((result & SCANRESULT_LINE_WIDTH_OK_BIT) == 0)
        {
            if (m_isWideLineCase && isMsaa)
            {
                // multisampled wide lines might not be supported
                rowScanResult[SCANRESULT_LINE_WIDTH_MSAA]++;
            }
            else if ((result & SCANRESULT_LINE_WIDTH_ERR_BIT) == 0 && (result & SCANRESULT_LINE_WIDTH_WARN_BIT) != 0)
            {
                rowScanResult[SCANRESULT_LINE_WIDTH_WARN]++;
            }
            else
                rowScanResult[SCANRESULT_LINE_WIDTH_ERR]++;
        }
    }

    // scan columns
    for (int x = de::max(verificationArea.x(), viewportPatternArea.x());
         x < de::min(verificationArea.z(), viewportPatternArea.z()); ++x)
    {
        const uint8_t result = scanColumn(viewportSurface.getAccess(), x, verificationArea.y(), verificationArea.w(),
                                          de::min(verificationArea.y(), viewportPatternArea.y()),
                                          de::min(verificationArea.w(), viewportPatternArea.w()),
                                          expectedHorizontalLines, messageLimitCounter);

        if ((result & SCANRESULT_NUM_LINES_OK_BIT) == 0)
            columnScanResult[SCANRESULT_NUM_LINES_ERR]++;
        if ((result & SCANRESULT_LINE_CONT_OK_BIT) == 0)
        {
            if ((result & SCANRESULT_LINE_CONT_WARN_BIT) != 0)
                columnScanResult[SCANRESULT_LINE_CONT_WARN]++;
            else
                columnScanResult[SCANRESULT_LINE_CONT_ERR]++;
        }
        else if ((result & SCANRESULT_LINE_WIDTH_OK_BIT) == 0)
        {
            if (m_isWideLineCase && isMsaa)
            {
                // multisampled wide lines might not be supported
                columnScanResult[SCANRESULT_LINE_WIDTH_MSAA]++;
            }
            else if ((result & SCANRESULT_LINE_WIDTH_ERR_BIT) == 0 && (result & SCANRESULT_LINE_WIDTH_WARN_BIT) != 0)
            {
                columnScanResult[SCANRESULT_LINE_WIDTH_WARN]++;
            }
            else
                columnScanResult[SCANRESULT_LINE_WIDTH_ERR]++;
        }
    }

    if (columnScanResult[SCANRESULT_LINE_WIDTH_ERR] != 0 || rowScanResult[SCANRESULT_LINE_WIDTH_ERR] != 0)
        anyError = true;
    else if (columnScanResult[SCANRESULT_LINE_CONT_ERR] != 0 || rowScanResult[SCANRESULT_LINE_CONT_ERR] != 0)
        anyError = true;
    else if (columnScanResult[SCANRESULT_LINE_WIDTH_MSAA] != 0 || rowScanResult[SCANRESULT_LINE_WIDTH_MSAA] != 0)
        msaaRelaxationRequired = true;
    else if (columnScanResult[SCANRESULT_LINE_WIDTH_WARN] != 0 || rowScanResult[SCANRESULT_LINE_WIDTH_WARN] != 0)
        hwIssueRelaxationRequired = true;
    else if (columnScanResult[SCANRESULT_NUM_LINES_ERR] != 0)
    {
        // found missing lines in a columnw and row line continuity check reported a warning (not an error) -> line width precision issue
        if (rowScanResult[SCANRESULT_LINE_CONT_ERR] == 0 && rowScanResult[SCANRESULT_LINE_CONT_WARN])
            hwIssueRelaxationRequired = true;
        else
            anyError = true;
    }
    else if (rowScanResult[SCANRESULT_NUM_LINES_ERR] != 0)
    {
        // found missing lines in a row and column line continuity check reported a warning (not an error) -> line width precision issue
        if (columnScanResult[SCANRESULT_LINE_CONT_ERR] == 0 && columnScanResult[SCANRESULT_LINE_CONT_WARN])
            hwIssueRelaxationRequired = true;
        else
            anyError = true;
    }

    if (anyError || msaaRelaxationRequired || hwIssueRelaxationRequired)
    {
        if (messageLimitCounter < 0)
            m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (-messageLimitCounter)
                               << " row/column error descriptions." << tcu::TestLog::EndMessage;

        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
                           << tcu::TestLog::EndImageSet;

        if (anyError)
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
        else if (hwIssueRelaxationRequired)
        {
            // Line width hw issue
            m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Line width verification failed");
        }
        else
        {
            // MSAA wide lines are optional
            m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Multisampled wide line verification failed");
        }
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Result image ok." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
                           << tcu::TestLog::EndImageSet;
    }
}

tcu::IVec2 LineRenderCase::getNumberOfLinesRange(int queryAreaBegin, int queryAreaEnd, float patternStart,
                                                 float patternSize, int viewportArea, QueryDirection queryDir) const
{
    // pattern is not symmetric due to mirroring
    const int patternStartNdx = (queryDir == DIRECTION_HORIZONTAL) ? ((m_hasGeometryStage) ? (1) : (0)) :
                                                                     ((m_hasTessellationStage) ? (1) : (0));
    const int patternEndNdx   = patternStartNdx + m_patternSide;

    int numLinesMin = 0;
    int numLinesMax = 0;

    for (int lineNdx = patternStartNdx; lineNdx < patternEndNdx; ++lineNdx)
    {
        const float linePos   = (patternStart + (float(lineNdx) / float(m_patternSide)) * patternSize) * 0.5f + 0.5f;
        const float lineWidth = (m_isWideLineCase) ? ((float)m_wideLineLineWidth) : (1.0f);

        if (linePos * (float)viewportArea > (float)queryAreaBegin + 1.0f &&
            linePos * (float)viewportArea < (float)queryAreaEnd - 1.0f)
        {
            // line center is within the area
            ++numLinesMin;
            ++numLinesMax;
        }
        else if (linePos * (float)viewportArea > (float)queryAreaBegin - lineWidth * 0.5f - 1.0f &&
                 linePos * (float)viewportArea < (float)queryAreaEnd + lineWidth * 0.5f + 1.0f)
        {
            // line could leak into area
            ++numLinesMax;
        }
    }

    return tcu::IVec2(numLinesMin, numLinesMax);
}

uint8_t LineRenderCase::scanRow(const tcu::ConstPixelBufferAccess &access, int row, int rowBegin, int rowEnd,
                                int rowViewportBegin, int rowViewportEnd, const tcu::IVec2 &numLines,
                                int &messageLimitCounter) const
{
    const bool numLinesOk      = checkAreaNumLines(access, tcu::IVec4(rowBegin, row, rowEnd - rowBegin, 1),
                                                   messageLimitCounter, SCAN_ROW_COMPONENT_NDX, numLines);
    const uint8_t lineWidthRes = checkLineWidths(access, tcu::IVec2(rowBegin, row), tcu::IVec2(rowEnd, row),
                                                 SCAN_ROW_COMPONENT_NDX, messageLimitCounter);
    const uint8_t lineContinuityRes =
        checkLineContinuity(access, tcu::IVec2(rowViewportBegin, row), tcu::IVec2(rowViewportEnd, row),
                            SCAN_COL_COMPONENT_NDX, messageLimitCounter);
    uint8_t result = 0;

    if (numLinesOk)
        result |= (uint8_t)SCANRESULT_NUM_LINES_OK_BIT;

    if (lineContinuityRes == 0)
        result |= (uint8_t)SCANRESULT_LINE_CONT_OK_BIT;
    else
        result |= lineContinuityRes;

    if (lineWidthRes == 0)
        result |= (uint8_t)SCANRESULT_LINE_WIDTH_OK_BIT;
    else
        result |= lineWidthRes;

    return result;
}

uint8_t LineRenderCase::scanColumn(const tcu::ConstPixelBufferAccess &access, int column, int columnBegin,
                                   int columnEnd, int columnViewportBegin, int columnViewportEnd,
                                   const tcu::IVec2 &numLines, int &messageLimitCounter) const
{
    const bool numLinesOk      = checkAreaNumLines(access, tcu::IVec4(column, columnBegin, 1, columnEnd - columnBegin),
                                                   messageLimitCounter, SCAN_COL_COMPONENT_NDX, numLines);
    const uint8_t lineWidthRes = checkLineWidths(access, tcu::IVec2(column, columnBegin), tcu::IVec2(column, columnEnd),
                                                 SCAN_COL_COMPONENT_NDX, messageLimitCounter);
    const uint8_t lineContinuityRes =
        checkLineContinuity(access, tcu::IVec2(column, columnViewportBegin), tcu::IVec2(column, columnViewportEnd),
                            SCAN_ROW_COMPONENT_NDX, messageLimitCounter);
    uint8_t result = 0;

    if (numLinesOk)
        result |= (uint8_t)SCANRESULT_NUM_LINES_OK_BIT;

    if (lineContinuityRes == 0)
        result |= (uint8_t)SCANRESULT_LINE_CONT_OK_BIT;
    else
        result |= lineContinuityRes;

    if (lineWidthRes == 0)
        result |= (uint8_t)SCANRESULT_LINE_WIDTH_OK_BIT;
    else
        result |= lineWidthRes;

    return result;
}

bool LineRenderCase::checkAreaNumLines(const tcu::ConstPixelBufferAccess &access, const tcu::IVec4 &area,
                                       int &messageLimitCounter, int componentNdx, const tcu::IVec2 &numLines) const
{
    // Num maxima == num lines
    const tcu::ConstPixelBufferAccess subAccess =
        tcu::getSubregion(access, area.x(), area.y(), 0, area.z(), area.w(), 1);
    const tcu::IVec2 numMinimaMaxima = getNumMinimaMaxima(subAccess, componentNdx);
    const int numMaxima              = numMinimaMaxima.y();

    // In valid range
    if (numMaxima >= numLines.x() && numMaxima <= numLines.y())
        return true;

    if (--messageLimitCounter < 0)
        return false;

    if (area.z() == 1)
        m_testCtx.getLog() << tcu::TestLog::Message << "On column " << area.x() << ", y: [" << area.y() << ","
                           << (area.y() + area.w()) << "):\n"
                           << "\tExpected [" << numLines.x() << ", " << numLines.y()
                           << "] lines but the number of lines in the area is " << numMaxima
                           << tcu::TestLog::EndMessage;
    else
        m_testCtx.getLog() << tcu::TestLog::Message << "On row " << area.y() << ", x: [" << area.x() << ","
                           << (area.x() + area.z()) << "):\n"
                           << "\tExpected [" << numLines.x() << ", " << numLines.y()
                           << "] lines but the number of lines in the area is " << numMaxima
                           << tcu::TestLog::EndMessage;

    return false;
}

tcu::IVec2 LineRenderCase::getNumMinimaMaxima(const tcu::ConstPixelBufferAccess &access, int componentNdx) const
{
    DE_ASSERT(access.getWidth() == 1 || access.getHeight() == 1);

    int previousValue = -1;
    int previousSign  = 0;
    int numMinima     = 0;
    int numMaxima     = 0;

    for (int y = 0; y < access.getHeight(); ++y)
        for (int x = 0; x < access.getWidth(); ++x)
        {
            const int componentValue = access.getPixelInt(x, y)[componentNdx];

            if (previousValue != -1)
            {
                const int sign = (componentValue > previousValue) ? (+1) :
                                 (componentValue < previousValue) ? (-1) :
                                                                    (0);

                // local minima/maxima in sign changes (zero signless)
                if (sign != 0 && sign == -previousSign)
                {
                    previousSign = sign;

                    if (sign > 0)
                        ++numMinima;
                    else
                        ++numMaxima;
                }
                else if (sign != 0 && previousSign == 0)
                {
                    previousSign = sign;

                    // local extreme at the start boundary
                    if (sign > 0)
                        ++numMinima;
                    else
                        ++numMaxima;
                }
            }

            previousValue = componentValue;
        }

    // local extreme at the end boundary
    if (previousSign > 0)
        ++numMaxima;
    else if (previousSign < 0)
        ++numMinima;
    else
    {
        ++numMaxima;
        ++numMinima;
    }

    return tcu::IVec2(numMinima, numMaxima);
}

uint8_t LineRenderCase::checkLineContinuity(const tcu::ConstPixelBufferAccess &access, const tcu::IVec2 &begin,
                                            const tcu::IVec2 &end, int componentNdx, int &messageLimitCounter) const
{
    bool line                = false;
    const tcu::IVec2 advance = (begin.x() == end.x()) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0));
    int missedPixels         = 0;
    int totalPixels          = 0;
    uint8_t errorMask        = 0;

    for (tcu::IVec2 cursor = begin; cursor != end; cursor += advance)
    {
        const bool hit = (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0);

        if (hit)
            line = true;
        else if (line && !hit)
        {
            // non-continuous line detected
            const tcu::IVec2 advanceNeighbor   = tcu::IVec2(1, 1) - advance;
            const tcu::IVec2 cursorNeighborPos = cursor + advanceNeighbor;
            const tcu::IVec2 cursorNeighborNeg = cursor - advanceNeighbor;
            // hw precision issues may lead to a line being non-straight -> check neighboring pixels
            if ((access.getPixelInt(cursorNeighborPos.x(), cursorNeighborPos.y())[componentNdx] == 0) &&
                (access.getPixelInt(cursorNeighborNeg.x(), cursorNeighborNeg.y())[componentNdx] == 0))
                ++missedPixels;
        }
        ++totalPixels;
    }

    if (missedPixels > 0)
    {
        if (--messageLimitCounter >= 0)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Found non-continuous "
                               << ((advance.x() == 1) ? ("horizontal") : ("vertical")) << " line near " << begin << ". "
                               << "Missed pixels: " << missedPixels << tcu::TestLog::EndMessage;
        }
        // allow 10% missing pixels for warning
        if (missedPixels <= deRoundFloatToInt32((float)totalPixels * 0.1f))
            errorMask = SCANRESULT_LINE_CONT_WARN_BIT;
        else
            errorMask = SCANRESULT_LINE_CONT_ERR_BIT;
    }

    return errorMask;
}

uint8_t LineRenderCase::checkLineWidths(const tcu::ConstPixelBufferAccess &access, const tcu::IVec2 &begin,
                                        const tcu::IVec2 &end, int componentNdx, int &messageLimitCounter) const
{
    const bool multisample    = m_context.getRenderTarget().getNumSamples() > 1;
    const int lineRenderWidth = (m_isWideLineCase) ? (m_wideLineLineWidth) : 1;
    const tcu::IVec2 lineWidthRange =
        (multisample) ? (tcu::IVec2(lineRenderWidth,
                                    lineRenderWidth + 1)) // multisampled "smooth" lines may spread to neighboring pixel
                        :
                        (tcu::IVec2(lineRenderWidth, lineRenderWidth));
    const tcu::IVec2 relaxedLineWidthRange = (tcu::IVec2(lineRenderWidth - 1, lineRenderWidth + 1));

    int lineWidth        = 0;
    bool bboxLimitedLine = false;
    uint8_t errorMask    = 0;

    const tcu::IVec2 advance = (begin.x() == end.x()) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0));

    // fragments before begin?
    if (access.getPixelInt(begin.x(), begin.y())[componentNdx] != 0)
    {
        bboxLimitedLine = true;

        for (tcu::IVec2 cursor = begin - advance;; cursor -= advance)
        {
            if (cursor.x() < 0 || cursor.y() < 0)
            {
                break;
            }
            else if (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0)
            {
                ++lineWidth;
            }
            else
                break;
        }
    }

    for (tcu::IVec2 cursor = begin; cursor != end; cursor += advance)
    {
        const bool hit = (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0);

        if (hit)
            ++lineWidth;
        else if (lineWidth)
        {
            // Line is allowed to be be thinner if it borders the bbox boundary (since part of the line might have been discarded).
            const bool incorrectLineWidth =
                (lineWidth < lineWidthRange.x() && !bboxLimitedLine) || (lineWidth > lineWidthRange.y());

            if (incorrectLineWidth)
            {
                const bool incorrectRelaxedLineWidth = (lineWidth < relaxedLineWidthRange.x() && !bboxLimitedLine) ||
                                                       (lineWidth > relaxedLineWidthRange.y());

                if (incorrectRelaxedLineWidth)
                    errorMask |= SCANRESULT_LINE_WIDTH_ERR_BIT;
                else
                    errorMask |= SCANRESULT_LINE_WIDTH_WARN_BIT;

                printLineWidthError(cursor, lineWidth, lineWidthRange, advance.x() == 0, messageLimitCounter);
            }

            lineWidth       = 0;
            bboxLimitedLine = false;
        }
    }

    // fragments after end?
    if (lineWidth)
    {
        for (tcu::IVec2 cursor = end;; cursor += advance)
        {
            if (cursor.x() >= access.getWidth() || cursor.y() >= access.getHeight())
            {
                if (lineWidth > lineWidthRange.y())
                {
                    if (lineWidth > relaxedLineWidthRange.y())
                        errorMask |= SCANRESULT_LINE_WIDTH_ERR_BIT;
                    else
                        errorMask |= SCANRESULT_LINE_WIDTH_WARN_BIT;

                    printLineWidthError(cursor, lineWidth, lineWidthRange, advance.x() == 0, messageLimitCounter);
                }

                break;
            }
            else if (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0)
            {
                ++lineWidth;
            }
            else if (lineWidth)
            {
                // only check that line width is not larger than expected. Line width may be smaller
                // since the scanning 'cursor' is now outside the bounding box.
                const bool incorrectLineWidth = (lineWidth > lineWidthRange.y());

                if (incorrectLineWidth)
                {
                    const bool incorrectRelaxedLineWidth = (lineWidth > relaxedLineWidthRange.y());

                    if (incorrectRelaxedLineWidth)
                        errorMask |= SCANRESULT_LINE_WIDTH_ERR_BIT;
                    else
                        errorMask |= SCANRESULT_LINE_WIDTH_WARN_BIT;

                    printLineWidthError(cursor, lineWidth, lineWidthRange, advance.x() == 0, messageLimitCounter);
                }

                lineWidth = 0;
            }
        }
    }

    return errorMask;
}

void LineRenderCase::printLineWidthError(const tcu::IVec2 &pos, int detectedLineWidth, const tcu::IVec2 &lineWidthRange,
                                         bool isHorizontal, int &messageLimitCounter) const
{
    if (--messageLimitCounter < 0)
        return;

    m_testCtx.getLog() << tcu::TestLog::Message << "Found incorrect line width near " << pos << ": ("
                       << ((isHorizontal) ? ("horizontal") : ("vertical")) << " line)\n"
                       << "\tExpected line width in range [" << lineWidthRange.x() << ", " << lineWidthRange.y()
                       << "] but found " << detectedLineWidth << tcu::TestLog::EndMessage;
}

class PointRenderCase : public BBoxRenderCase
{
public:
    enum
    {
        POINTFLAG_WIDE = 1u << FLAGBIT_USER_BIT, //!< use wide points
    };
    struct GeneratedPoint
    {
        tcu::Vec2 center;
        int size;
        bool even;
    };

    PointRenderCase(Context &context, const char *name, const char *description, uint32_t flags);
    ~PointRenderCase(void);

private:
    enum ResultPointType
    {
        POINT_FULL = 0,
        POINT_PARTIAL
    };

    void init(void);
    void deinit(void);

    std::string genVertexSource(void) const;
    std::string genFragmentSource(void) const;
    std::string genTessellationControlSource(void) const;
    std::string genTessellationEvaluationSource(void) const;
    std::string genGeometrySource(void) const;

    IterationConfig generateConfig(int iteration, const tcu::IVec2 &renderTargetSize) const;
    void generateAttributeData(void);
    void getAttributeData(std::vector<tcu::Vec4> &data) const;
    void renderTestPattern(const IterationConfig &config);
    void verifyRenderResult(const IterationConfig &config);

    void genReferencePointData(const IterationConfig &config, std::vector<GeneratedPoint> &data) const;
    bool verifyNarrowPointPattern(const tcu::Surface &viewport, const std::vector<GeneratedPoint> &refPoints,
                                  const ProjectedBBox &bbox, int &logFloodCounter);
    bool verifyWidePointPattern(const tcu::Surface &viewport, const std::vector<GeneratedPoint> &refPoints,
                                const ProjectedBBox &bbox, int &logFloodCounter);
    bool verifyWidePoint(const tcu::Surface &viewport, const GeneratedPoint &refPoint, const ProjectedBBox &bbox,
                         ResultPointType pointType, int &logFloodCounter);
    bool verifyWidePointAt(const tcu::IVec2 &pointPos, const tcu::Surface &viewport, const GeneratedPoint &refPoint,
                           const tcu::IVec4 &bbox, ResultPointType pointType, int componentNdx, int &logFloodCounter);
    tcu::IVec2 scanPointWidthAt(const tcu::IVec2 &pointPos, const tcu::Surface &viewport, int expectedPointSize,
                                int componentNdx) const;

    const int m_numStripes;
    const bool m_isWidePointCase;
    std::vector<tcu::Vec4> m_attribData;
};

PointRenderCase::PointRenderCase(Context &context, const char *name, const char *description, uint32_t flags)
    : BBoxRenderCase(context, name, description, 12, flags)
    , m_numStripes(4)
    , m_isWidePointCase((flags & POINTFLAG_WIDE) != 0)
{
}

PointRenderCase::~PointRenderCase(void)
{
}

void PointRenderCase::init(void)
{
    if (m_isWidePointCase)
    {
        const bool supportsGL45 =
            glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

        if (!supportsGL45)
        {
            // extensions
            if (m_hasGeometryStage && !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_point_size"))
                throw tcu::NotSupportedError("Test requires GL_EXT_geometry_point_size extension");
            if (m_hasTessellationStage && !m_hasGeometryStage &&
                !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_point_size"))
                throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_point_size extension");
        }

        // Enable program point size for desktop GL
        if (supportsGL45)
        {
            const glw::Functions &gl = m_context.getRenderContext().getFunctions();
            gl.enable(GL_PROGRAM_POINT_SIZE);
        }

        // point size range
        {
            glw::GLfloat pointSizeRange[2] = {0.0f, 0.0f};
            m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);

            if (pointSizeRange[1] < 5.0f)
                throw tcu::NotSupportedError("Test requires point size 5.0");
        }
    }

    m_testCtx.getLog()
        << tcu::TestLog::Message << "Rendering point pattern to "
        << ((m_renderTarget == RENDERTARGET_DEFAULT) ? ("default frame buffer") : ("fbo")) << ".\n"
        << "Half of the points are green, half blue. Using additive blending.\n"
        << "Points are in random order, varying pattern size and location for each iteration.\n"
        << "Marking all discardable fragments (fragments outside the bounding box) with a fully saturated red channel."
        << tcu::TestLog::EndMessage;

    generateAttributeData();

    BBoxRenderCase::init();
}

void PointRenderCase::deinit(void)
{
    // clear data
    m_attribData = std::vector<tcu::Vec4>();

    // deinit parent
    BBoxRenderCase::deinit();
}

std::string PointRenderCase::genVertexSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in highp vec4 a_position;\n"
           "in highp vec4 a_color;\n"
           "out highp vec4 vtx_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "\n";
    if (!m_hasTessellationStage)
    {
        DE_ASSERT(m_useGlobalState);
        buf << "uniform highp vec4 u_primitiveBBoxMin;\n"
               "uniform highp vec4 u_primitiveBBoxMax;\n"
               "\n"
               "flat out highp float v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_expansionSize;\n"
               "flat out highp vec3 v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMin;\n"
               "flat out highp vec3 v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMax;\n"
               "\n";
    }

    buf << "void main()\n"
           "{\n"
           "    highp vec2 patternOffset = u_posScale.xy;\n"
           "    highp vec2 patternScale = u_posScale.zw;\n"
           "    highp float pointSize = "
        << ((m_isWidePointCase && !m_hasTessellationStage && !m_hasGeometryStage) ?
                ("(a_color.g > 0.0) ? (5.0) : (3.0)") :
                ("1.0"))
        << ";\n"
        << "    gl_Position = vec4(a_position.xy * patternScale + patternOffset, a_position.z, a_position.w);\n"
           "    gl_PointSize = pointSize;\n"
           "    vtx_color = a_color;\n";

    if (!m_hasTessellationStage)
    {
        DE_ASSERT(m_useGlobalState);
        buf << "\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_expansionSize = pointSize;\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMin =\n"
               "        min(vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / "
               "u_primitiveBBoxMin.w,\n"
               "            vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / "
               "u_primitiveBBoxMax.w);\n"
               "    v_"
            << (m_hasGeometryStage ? "geo_" : "")
            << "bbox_clipMax =\n"
               "        min(vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / "
               "u_primitiveBBoxMin.w,\n"
               "            vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / "
               "u_primitiveBBoxMax.w);\n";
    }

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

std::string PointRenderCase::genFragmentSource(void) const
{
    const char *const colorInputName = (m_hasGeometryStage)     ? ("geo_color") :
                                       (m_hasTessellationStage) ? ("tess_color") :
                                                                  ("vtx_color");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in mediump vec4 "
        << colorInputName
        << ";\n"
           "layout(location = 0) out mediump vec4 o_color;\n"
        << genShaderFunction(SHADER_FUNC_INSIDE_BBOX)
        << "\n"
           "void main()\n"
           "{\n"
           "    mediump vec4 baseColor = "
        << colorInputName
        << ";\n"
           "    mediump float redChannel;\n"
           "    if (fragmentInsideTheBBox(gl_FragCoord.z))\n"
           "        redChannel = 0.0;\n"
           "    else\n"
           "        redChannel = 1.0;\n"
           "    o_color = vec4(redChannel, baseColor.g, baseColor.b, baseColor.a);\n"
           "}\n";

    return buf.str();
}

std::string PointRenderCase::genTessellationControlSource(void) const
{
    const bool tessellationWidePoints = (m_isWidePointCase) && (!m_hasGeometryStage);
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${ARB_ES32_COMPATIBILITY_REQUIRE}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${PRIMITIVE_BOUNDING_BOX_REQUIRE}\n"
        << ((tessellationWidePoints) ? ("${TESSELLATION_POINT_SIZE_REQUIRE}\n") : (""))
        << "layout(vertices=1) out;"
           "\n"
           "in highp vec4 vtx_color[];\n"
           "out highp vec4 tess_ctrl_color[];\n"
           "uniform highp float u_tessellationLevel;\n"
           "uniform highp vec4 u_posScale;\n";

    if (!m_calcPerPrimitiveBBox)
    {
        buf << "uniform highp vec4 u_primitiveBBoxMin;\n"
               "uniform highp vec4 u_primitiveBBoxMax;\n";
    }

    buf << "patch out highp vec3 vp_bbox_clipMin;\n"
           "patch out highp vec3 vp_bbox_clipMax;\n";

    if (m_calcPerPrimitiveBBox)
    {
        buf << "\n";
        if (m_hasGeometryStage)
            buf << genShaderFunction(SHADER_FUNC_MIRROR_X);
        buf << genShaderFunction(SHADER_FUNC_MIRROR_Y);

        buf << "vec4 transformVec(in highp vec4 p)\n"
               "{\n"
               "    return "
            << ((m_hasGeometryStage) ? ("mirrorX(mirrorY(p))") : ("mirrorY(p)"))
            << ";\n"
               "}\n";
    }

    buf << "\n"
           "void main()\n"
           "{\n"
           "    // convert to nonsensical coordinates, just in case\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position.wzxy;\n"
           "    tess_ctrl_color[gl_InvocationID] = vtx_color[gl_InvocationID];\n"
           "\n"
           "    gl_TessLevelOuter[0] = u_tessellationLevel;\n"
           "    gl_TessLevelOuter[1] = u_tessellationLevel;\n"
           "    gl_TessLevelOuter[2] = u_tessellationLevel;\n"
           "    gl_TessLevelOuter[3] = u_tessellationLevel;\n"
           "    gl_TessLevelInner[0] = 0.8; // will be rounded up to 1\n"
           "    gl_TessLevelInner[1] = 0.8; // will be rounded up to 1\n";

    if (m_calcPerPrimitiveBBox)
    {
        buf << "\n";

        if (m_hasGeometryStage)
            buf << "    const vec2 minExpansion = vec2(0.07 + 0.05, 0.07 + 0.02); // eval and geometry shader\n"
                   "    const vec2 maxExpansion = vec2(0.07 + 0.05, 0.07 + 0.03); // eval and geometry shader\n";
        else
            buf << "    const vec2 minExpansion = vec2(0.07, 0.07); // eval shader\n"
                   "    const vec2 maxExpansion = vec2(0.07, 0.07); // eval shader\n";

        buf << "    highp vec2 patternScale = u_posScale.zw;\n"
               "    highp vec4 bboxMin = transformVec(gl_in[0].gl_Position) - vec4(minExpansion * patternScale, 0.0, "
               "0.0);\n"
               "    highp vec4 bboxMax = transformVec(gl_in[0].gl_Position) + vec4(maxExpansion * patternScale, 0.0, "
               "0.0);\n";
    }
    else
    {
        buf << "\n"
               "    highp vec4 bboxMin = u_primitiveBBoxMin;\n"
               "    highp vec4 bboxMax = u_primitiveBBoxMax;\n";
    }
    if (!m_useGlobalState)
        buf << "\n"
               "    ${PRIM_GL_BOUNDING_BOX}[0] = bboxMin;\n"
               "    ${PRIM_GL_BOUNDING_BOX}[1] = bboxMax;\n";

    buf << "    vp_bbox_clipMin = min(vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMin.w,\n"
           "                          vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMax.w);\n"
           "    vp_bbox_clipMax = max(vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMin.w,\n"
           "                          vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMax.w);\n"
           "}\n";

    return buf.str();
}

std::string PointRenderCase::genTessellationEvaluationSource(void) const
{
    const bool tessellationWidePoints = (m_isWidePointCase) && (!m_hasGeometryStage);
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
        << ((tessellationWidePoints) ? ("${TESSELLATION_POINT_SIZE_REQUIRE}\n") : (""))
        << "layout(quads, point_mode) in;"
           "\n"
           "in highp vec4 tess_ctrl_color[];\n"
           "out highp vec4 tess_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "\n"
           "patch in highp vec3 vp_bbox_clipMin;\n"
           "patch in highp vec3 vp_bbox_clipMax;\n"
        << ((!m_hasGeometryStage) ? ("flat out highp float v_bbox_expansionSize;\n") : ("")) << "flat out highp vec3 v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMin;\n"
           "flat out highp vec3 v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMax;\n"
           "\n"
        << genShaderFunction(SHADER_FUNC_MIRROR_Y)
        << "void main()\n"
           "{\n"
           "    // non-trivial tessellation evaluation shader, convert from nonsensical coords, flip vertically\n"
           "    highp vec2 patternScale = u_posScale.zw;\n"
           "    highp vec4 offset = vec4((gl_TessCoord.xy * 2.0 - vec2(1.0)) * 0.07 * patternScale, 0.0, 0.0);\n"
           "    highp float pointSize = "
        << ((tessellationWidePoints) ? ("(tess_ctrl_color[0].g > 0.0) ? (5.0) : (3.0)") : ("1.0"))
        << ";\n"
           "    gl_Position = mirrorY(gl_in[0].gl_Position.zwyx + offset);\n";

    if (tessellationWidePoints)
        buf << "    gl_PointSize = pointSize;\n";

    buf << "    tess_color = tess_ctrl_color[0];\n"
        << ((!m_hasGeometryStage) ? ("v_bbox_expansionSize = pointSize;\n") : ("")) << "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMin = vp_bbox_clipMin;\n"
           "    v_"
        << (m_hasGeometryStage ? "geo_" : "")
        << "bbox_clipMax = vp_bbox_clipMax;\n"
           "}\n";

    return buf.str();
}

std::string PointRenderCase::genGeometrySource(void) const
{
    const char *const colorInputName = (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GEOMETRY_SHADER_REQUIRE}\n"
        << ((m_isWidePointCase) ? ("${GEOMETRY_POINT_SIZE}\n") : (""))
        << "layout(points) in;\n"
           "layout(max_vertices=3, points) out;\n"
           "\n"
           "in highp vec4 "
        << colorInputName
        << "[1];\n"
           "out highp vec4 geo_color;\n"
           "uniform highp vec4 u_posScale;\n"
           "\n"
           "flat in highp vec3 v_geo_bbox_clipMin[1];\n"
           "flat in highp vec3 v_geo_bbox_clipMax[1];\n"
           "flat out highp vec3 v_bbox_clipMin;\n"
           "flat out highp vec3 v_bbox_clipMax;\n"
           "flat out highp float v_bbox_expansionSize;\n"
           "\n"
        << genShaderFunction(SHADER_FUNC_MIRROR_X)
        << "\n"
           "void main()\n"
           "{\n"
           "    // Non-trivial geometry shader: 1-to-3 amplification, mirror horizontally\n"
           "    highp vec4 p0 = mirrorX(gl_in[0].gl_Position);\n"
           "    highp vec4 pointColor = "
        << colorInputName
        << "[0];\n"
           "    highp vec2 patternScale = u_posScale.zw;\n"
           "    highp float pointSize = "
        << (m_isWidePointCase ? ("(pointColor.g > 0.0) ? (5.0) : (3.0)") : ("1.0"))
        << ";\n"
           "\n"
           "    highp vec4 offsets[3] =\n"
           "        vec4[3](\n"
           "            vec4( 0.05 * patternScale.x, 0.03 * patternScale.y, 0.0, 0.0),\n"
           "            vec4(-0.01 * patternScale.x,-0.02 * patternScale.y, 0.0, 0.0),\n"
           "            vec4(-0.05 * patternScale.x, 0.02 * patternScale.y, 0.0, 0.0)\n"
           "        );\n"
           "    for (int ndx = 0; ndx < 3; ++ndx)\n"
           "    {\n"
           "        gl_Position = p0 + offsets[ndx];\n";

    if (m_isWidePointCase)
        buf << "        gl_PointSize = pointSize;\n";

    buf << "        v_bbox_clipMin = v_geo_bbox_clipMin[0];\n"
           "        v_bbox_clipMax = v_geo_bbox_clipMax[0];\n"
           "        v_bbox_expansionSize = pointSize;\n"
           "        geo_color = pointColor;\n"
           "        EmitVertex();\n"
           "    }\n"
           "}\n";

    return buf.str();
}

PointRenderCase::IterationConfig PointRenderCase::generateConfig(int iteration,
                                                                 const tcu::IVec2 &renderTargetSize) const
{
    IterationConfig config = generateRandomConfig(0xDEDEDEu * (uint32_t)iteration, renderTargetSize);

    // equal or larger -> expand according to shader expansion
    if (m_bboxSize == BBOXSIZE_EQUAL || m_bboxSize == BBOXSIZE_LARGER)
    {
        const tcu::Vec2 patternScale = config.patternSize;

        if (m_hasTessellationStage)
        {
            config.bbox.min -= tcu::Vec4(0.07f * patternScale.x(), 0.07f * patternScale.y(), 0.0f, 0.0f);
            config.bbox.max += tcu::Vec4(0.07f * patternScale.x(), 0.07f * patternScale.y(), 0.0f, 0.0f);
        }
        if (m_hasGeometryStage)
        {
            config.bbox.min -= tcu::Vec4(0.05f * patternScale.x(), 0.02f * patternScale.y(), 0.0f, 0.0f);
            config.bbox.max += tcu::Vec4(0.05f * patternScale.x(), 0.03f * patternScale.y(), 0.0f, 0.0f);
        }
    }

    return config;
}

void PointRenderCase::generateAttributeData(void)
{
    const tcu::Vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
    const tcu::Vec4 blue(0.0f, 0.0f, 1.0f, 1.0f);
    std::vector<int> cellOrder(m_numStripes * m_numStripes * 2);
    de::Random rnd(0xDE22446);

    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
        cellOrder[ndx] = ndx;
    rnd.shuffle(cellOrder.begin(), cellOrder.end());

    m_attribData.resize(cellOrder.size() * 2);
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
    {
        const int pointID    = cellOrder[ndx];
        const int direction  = pointID & 0x01;
        const int majorCoord = (pointID >> 1) / m_numStripes;
        const int minorCoord = (pointID >> 1) % m_numStripes;

        if (direction)
        {
            m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
                tcu::Vec4(float(minorCoord) / float(m_numStripes), float(majorCoord) / float(m_numStripes), 0.0f, 1.0f);
            m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = green;
        }
        else
        {
            m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] =
                tcu::Vec4(((float)majorCoord + 0.5f) / float(m_numStripes),
                          ((float)minorCoord + 0.5f) / float(m_numStripes), 0.0f, 1.0f);
            m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = blue;
        }
    }
}

void PointRenderCase::getAttributeData(std::vector<tcu::Vec4> &data) const
{
    data = m_attribData;
}

void PointRenderCase::renderTestPattern(const IterationConfig &config)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    setupRender(config);

    if (m_hasTessellationStage)
    {
        const glw::GLint tessLevelPos = gl.getUniformLocation(m_program->getProgram(), "u_tessellationLevel");
        const glw::GLfloat tessLevel  = 0.8f; // will be rounded up

        TCU_CHECK(tessLevelPos != -1);

        m_testCtx.getLog() << tcu::TestLog::Message << "u_tessellationLevel = " << tessLevel
                           << tcu::TestLog::EndMessage;

        gl.uniform1f(tessLevelPos, tessLevel);
        gl.patchParameteri(GL_PATCH_VERTICES, 1);
        GLU_EXPECT_NO_ERROR(gl.getError(), "patch param");
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering pattern." << tcu::TestLog::EndMessage;

    gl.enable(GL_BLEND);
    gl.blendFunc(GL_ONE, GL_ONE);
    gl.blendEquation(GL_FUNC_ADD);

    gl.drawArrays((m_hasTessellationStage) ? (GL_PATCHES) : (GL_POINTS), 0, m_numStripes * m_numStripes * 2);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

void PointRenderCase::verifyRenderResult(const IterationConfig &config)
{
    const glw::Functions &gl          = m_context.getRenderContext().getFunctions();
    const ProjectedBBox projectedBBox = projectBoundingBox(config.bbox);
    const tcu::IVec4 viewportBBoxArea = getViewportBoundingBoxArea(projectedBBox, config.viewportSize);

    tcu::Surface viewportSurface(config.viewportSize.x(), config.viewportSize.y());
    int logFloodCounter = 8;
    bool anyError;
    std::vector<GeneratedPoint> refPoints;

    if (!m_calcPerPrimitiveBBox)
        m_testCtx.getLog() << tcu::TestLog::Message << "Projected bounding box: (clip space)\n"
                           << "\tx: [" << projectedBBox.min.x() << "," << projectedBBox.max.x() << "]\n"
                           << "\ty: [" << projectedBBox.min.y() << "," << projectedBBox.max.y() << "]\n"
                           << "\tz: [" << projectedBBox.min.z() << "," << projectedBBox.max.z() << "]\n"
                           << "In viewport coordinates:\n"
                           << "\tx: [" << viewportBBoxArea.x() << ", " << viewportBBoxArea.z() << "]\n"
                           << "\ty: [" << viewportBBoxArea.y() << ", " << viewportBBoxArea.w() << "]\n"
                           << "Verifying render results within the bounding box:\n"
                           << tcu::TestLog::EndMessage;
    else
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying render result:" << tcu::TestLog::EndMessage;

    if (m_fbo)
        gl.bindFramebuffer(GL_READ_FRAMEBUFFER, **m_fbo);
    glu::readPixels(m_context.getRenderContext(), config.viewportPos.x(), config.viewportPos.y(),
                    viewportSurface.getAccess());

    genReferencePointData(config, refPoints);

    if (m_isWidePointCase)
        anyError = verifyWidePointPattern(viewportSurface, refPoints, projectedBBox, logFloodCounter);
    else
        anyError = verifyNarrowPointPattern(viewportSurface, refPoints, projectedBBox, logFloodCounter);

    if (anyError)
    {
        if (logFloodCounter < 0)
            m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (-logFloodCounter) << " error descriptions."
                               << tcu::TestLog::EndMessage;

        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
                           << tcu::TestLog::EndImageSet;

        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Result image ok." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
                           << tcu::TestLog::EndImageSet;
    }
}

struct PointSorter
{
    bool operator()(const PointRenderCase::GeneratedPoint &a, const PointRenderCase::GeneratedPoint &b) const
    {
        if (a.center.y() < b.center.y())
            return true;
        else if (a.center.y() > b.center.y())
            return false;
        else
            return (a.center.x() < b.center.x());
    }
};

void PointRenderCase::genReferencePointData(const IterationConfig &config, std::vector<GeneratedPoint> &data) const
{
    std::vector<GeneratedPoint> currentPoints;

    // vertex shader
    currentPoints.resize(m_attribData.size() / 2);
    for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
    {
        currentPoints[ndx].center = m_attribData[ndx * 2].swizzle(0, 1);
        currentPoints[ndx].even   = (m_attribData[ndx * 2 + 1].y() == 1.0f); // is green
        currentPoints[ndx].size   = ((m_isWidePointCase) ? ((currentPoints[ndx].even) ? 5 : 3) : 1);
    }

    // tessellation
    if (m_hasTessellationStage)
    {
        std::vector<GeneratedPoint> tessellatedPoints;

        tessellatedPoints.resize(currentPoints.size() * 4);
        for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
        {
            const tcu::Vec2 position =
                tcu::Vec2(currentPoints[ndx].center.x(), 1.0f - currentPoints[ndx].center.y()); // mirror Y

            tessellatedPoints[4 * ndx + 0].center = position + tcu::Vec2(-0.07f, -0.07f);
            tessellatedPoints[4 * ndx + 0].size   = currentPoints[ndx].size;
            tessellatedPoints[4 * ndx + 0].even   = currentPoints[ndx].even;

            tessellatedPoints[4 * ndx + 1].center = position + tcu::Vec2(0.07f, -0.07f);
            tessellatedPoints[4 * ndx + 1].size   = currentPoints[ndx].size;
            tessellatedPoints[4 * ndx + 1].even   = currentPoints[ndx].even;

            tessellatedPoints[4 * ndx + 2].center = position + tcu::Vec2(0.07f, 0.07f);
            tessellatedPoints[4 * ndx + 2].size   = currentPoints[ndx].size;
            tessellatedPoints[4 * ndx + 2].even   = currentPoints[ndx].even;

            tessellatedPoints[4 * ndx + 3].center = position + tcu::Vec2(-0.07f, 0.07f);
            tessellatedPoints[4 * ndx + 3].size   = currentPoints[ndx].size;
            tessellatedPoints[4 * ndx + 3].even   = currentPoints[ndx].even;
        }

        currentPoints.swap(tessellatedPoints);
    }

    // geometry
    if (m_hasGeometryStage)
    {
        std::vector<GeneratedPoint> geometryShadedPoints;

        geometryShadedPoints.resize(currentPoints.size() * 3);
        for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
        {
            const tcu::Vec2 position =
                tcu::Vec2(1.0f - currentPoints[ndx].center.x(), currentPoints[ndx].center.y()); // mirror X

            geometryShadedPoints[3 * ndx + 0].center = position + tcu::Vec2(0.05f, 0.03f);
            geometryShadedPoints[3 * ndx + 0].size   = currentPoints[ndx].size;
            geometryShadedPoints[3 * ndx + 0].even   = currentPoints[ndx].even;

            geometryShadedPoints[3 * ndx + 1].center = position + tcu::Vec2(-0.01f, -0.02f);
            geometryShadedPoints[3 * ndx + 1].size   = currentPoints[ndx].size;
            geometryShadedPoints[3 * ndx + 1].even   = currentPoints[ndx].even;

            geometryShadedPoints[3 * ndx + 2].center = position + tcu::Vec2(-0.05f, 0.02f);
            geometryShadedPoints[3 * ndx + 2].size   = currentPoints[ndx].size;
            geometryShadedPoints[3 * ndx + 2].even   = currentPoints[ndx].even;
        }

        currentPoints.swap(geometryShadedPoints);
    }

    // sort from left to right, top to bottom
    std::sort(currentPoints.begin(), currentPoints.end(), PointSorter());

    // map to pattern space
    for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
        currentPoints[ndx].center = currentPoints[ndx].center * config.patternSize + config.patternPos;

    currentPoints.swap(data);
}

bool PointRenderCase::verifyNarrowPointPattern(const tcu::Surface &viewport,
                                               const std::vector<GeneratedPoint> &refPoints, const ProjectedBBox &bbox,
                                               int &logFloodCounter)
{
    bool anyError = false;

    // check that there is something near each sample
    for (int pointNdx = 0; pointNdx < (int)refPoints.size(); ++pointNdx)
    {
        const float epsilon            = 1.0e-6f;
        const GeneratedPoint &refPoint = refPoints[pointNdx];

        // skip points not in the the bbox, treat boundary as "in"
        if (refPoint.center.x() < bbox.min.x() - epsilon || refPoint.center.y() < bbox.min.y() - epsilon ||
            refPoint.center.x() > bbox.max.x() + epsilon || refPoint.center.y() > bbox.max.y() + epsilon)
            continue;
        else
        {
            // transform to viewport coords
            const tcu::IVec2 pixelCenter(
                deRoundFloatToInt32((refPoint.center.x() * 0.5f + 0.5f) * (float)viewport.getWidth()),
                deRoundFloatToInt32((refPoint.center.y() * 0.5f + 0.5f) * (float)viewport.getHeight()));

            // find rasterized point in the result
            if (pixelCenter.x() < 1 || pixelCenter.y() < 1 || pixelCenter.x() >= viewport.getWidth() - 1 ||
                pixelCenter.y() >= viewport.getHeight() - 1)
            {
                // viewport boundary, assume point is fine
            }
            else
            {
                const int componentNdx = (refPoint.even) ? (1) : (2); // analyze either green or blue channel
                bool foundResult       = false;

                // check neighborhood
                for (int dy = -1; dy < 2 && !foundResult; ++dy)
                    for (int dx = -1; dx < 2 && !foundResult; ++dx)
                    {
                        const tcu::IVec2 testPos(pixelCenter.x() + dx, pixelCenter.y() + dy);
                        const tcu::RGBA color = viewport.getPixel(testPos.x(), testPos.y());

                        if (color.toIVec()[componentNdx] > 0)
                            foundResult = true;
                    }

                if (!foundResult)
                {
                    anyError = true;

                    if (--logFloodCounter >= 0)
                    {
                        m_testCtx.getLog() << tcu::TestLog::Message << "Missing point near " << pixelCenter
                                           << ", vertex coordinates=" << refPoint.center.swizzle(0, 1) << "."
                                           << tcu::TestLog::EndMessage;
                    }
                }
            }
        }
    }

    return anyError;
}

bool PointRenderCase::verifyWidePointPattern(const tcu::Surface &viewport, const std::vector<GeneratedPoint> &refPoints,
                                             const ProjectedBBox &bbox, int &logFloodCounter)
{
    bool anyError = false;

    // check that there is something near each sample
    for (int pointNdx = 0; pointNdx < (int)refPoints.size(); ++pointNdx)
    {
        const GeneratedPoint &refPoint = refPoints[pointNdx];

        if (refPoint.center.x() >= bbox.min.x() && refPoint.center.y() >= bbox.min.y() &&
            refPoint.center.x() <= bbox.max.x() && refPoint.center.y() <= bbox.max.y())
        {
            // point fully in the bounding box
            anyError |= !verifyWidePoint(viewport, refPoint, bbox, POINT_FULL, logFloodCounter);
        }
        else if (refPoint.center.x() >= bbox.min.x() + (float)refPoint.size / 2.0f &&
                 refPoint.center.y() >= bbox.min.y() - (float)refPoint.size / 2.0f &&
                 refPoint.center.x() <= bbox.max.x() + (float)refPoint.size / 2.0f &&
                 refPoint.center.y() <= bbox.max.y() - (float)refPoint.size / 2.0f)
        {
            // point leaks into bounding box
            anyError |= !verifyWidePoint(viewport, refPoint, bbox, POINT_PARTIAL, logFloodCounter);
        }
    }

    return anyError;
}

bool PointRenderCase::verifyWidePoint(const tcu::Surface &viewport, const GeneratedPoint &refPoint,
                                      const ProjectedBBox &bbox, ResultPointType pointType, int &logFloodCounter)
{
    const int componentNdx       = (refPoint.even) ? (1) : (2);
    const int halfPointSizeCeil  = (refPoint.size + 1) / 2;
    const int halfPointSizeFloor = (refPoint.size + 1) / 2;
    const tcu::IVec4 viewportBBoxArea =
        getViewportBoundingBoxArea(bbox, tcu::IVec2(viewport.getWidth(), viewport.getHeight()), (float)refPoint.size);
    const tcu::IVec4 verificationArea = tcu::IVec4(de::max(viewportBBoxArea.x(), 0), de::max(viewportBBoxArea.y(), 0),
                                                   de::min(viewportBBoxArea.z(), viewport.getWidth()),
                                                   de::min(viewportBBoxArea.w(), viewport.getHeight()));
    const tcu::IVec2 pointPos =
        tcu::IVec2(deRoundFloatToInt32((refPoint.center.x() * 0.5f + 0.5f) * (float)viewport.getWidth()),
                   deRoundFloatToInt32((refPoint.center.y() * 0.5f + 0.5f) * (float)viewport.getHeight()));

    // find any fragment within the point that is inside the bbox, start search at the center

    if (pointPos.x() >= verificationArea.x() && pointPos.y() >= verificationArea.y() &&
        pointPos.x() < verificationArea.z() && pointPos.y() < verificationArea.w())
    {
        if (viewport.getPixel(pointPos.x(), pointPos.y()).toIVec()[componentNdx])
            return verifyWidePointAt(pointPos, viewport, refPoint, verificationArea, pointType, componentNdx,
                                     logFloodCounter);
    }

    for (int dy = -halfPointSizeCeil; dy <= halfPointSizeCeil; ++dy)
        for (int dx = -halfPointSizeCeil; dx <= halfPointSizeCeil; ++dx)
        {
            const tcu::IVec2 testPos = pointPos + tcu::IVec2(dx, dy);

            if (dx == 0 && dy == 0)
                continue;

            if (testPos.x() >= verificationArea.x() && testPos.y() >= verificationArea.y() &&
                testPos.x() < verificationArea.z() && testPos.y() < verificationArea.w())
            {
                if (viewport.getPixel(testPos.x(), testPos.y()).toIVec()[componentNdx])
                    return verifyWidePointAt(testPos, viewport, refPoint, verificationArea, pointType, componentNdx,
                                             logFloodCounter);
            }
        }

    // could not find point, this is only ok near boundaries
    if (pointPos.x() + halfPointSizeFloor < verificationArea.x() - 1 ||
        pointPos.y() + halfPointSizeFloor < verificationArea.y() - 1 ||
        pointPos.x() - halfPointSizeFloor >= verificationArea.z() - 1 ||
        pointPos.y() - halfPointSizeFloor >= verificationArea.w() - 1)
        return true;

    if (--logFloodCounter >= 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Missing wide point near " << pointPos
                           << ", vertex coordinates=" << refPoint.center.swizzle(0, 1) << "."
                           << tcu::TestLog::EndMessage;
    }

    return false;
}

bool PointRenderCase::verifyWidePointAt(const tcu::IVec2 &pointPos, const tcu::Surface &viewport,
                                        const GeneratedPoint &refPoint, const tcu::IVec4 &bbox,
                                        ResultPointType pointType, int componentNdx, int &logFloodCounter)
{
    const int expectedPointSize = refPoint.size;
    bool viewportClippedTop     = false;
    bool viewportClippedBottom  = false;
    bool primitiveClippedTop    = false;
    bool primitiveClippedBottom = false;
    std::vector<tcu::IVec2> widthsUpwards;
    std::vector<tcu::IVec2> widthsDownwards;
    std::vector<tcu::IVec2> widths;

    // search upwards
    for (int y = pointPos.y();; --y)
    {
        if (y < bbox.y() || y < 0)
        {
            if (y < bbox.y())
                primitiveClippedTop = true;
            if (y < 0)
                viewportClippedTop = true;
            break;
        }
        else if (pointPos.y() - y > expectedPointSize)
        {
            // no need to go further than point height
            break;
        }
        else if (viewport.getPixel(pointPos.x(), y).toIVec()[componentNdx] == 0)
        {
            break;
        }
        else
        {
            widthsUpwards.push_back(
                scanPointWidthAt(tcu::IVec2(pointPos.x(), y), viewport, expectedPointSize, componentNdx));
        }
    }

    // top is clipped
    if ((viewportClippedTop || (pointType == POINT_PARTIAL && primitiveClippedTop)) && !widthsUpwards.empty())
    {
        const tcu::IVec2 &range = widthsUpwards.back();
        const bool squareFits   = (range.y() - range.x() + 1) >= expectedPointSize;
        const bool widthClipped = (pointType == POINT_PARTIAL) && (range.x() <= bbox.x() || range.y() >= bbox.z());

        if (squareFits || widthClipped)
            return true;
    }

    // and downwards
    for (int y = pointPos.y() + 1;; ++y)
    {
        if (y >= bbox.w() || y >= viewport.getHeight())
        {
            if (y >= bbox.w())
                primitiveClippedBottom = true;
            if (y >= viewport.getHeight())
                viewportClippedBottom = true;
            break;
        }
        else if (y - pointPos.y() > expectedPointSize)
        {
            // no need to go further than point height
            break;
        }
        else if (viewport.getPixel(pointPos.x(), y).toIVec()[componentNdx] == 0)
        {
            break;
        }
        else
        {
            widthsDownwards.push_back(
                scanPointWidthAt(tcu::IVec2(pointPos.x(), y), viewport, expectedPointSize, componentNdx));
        }
    }

    // bottom is clipped
    if ((viewportClippedBottom || (pointType == POINT_PARTIAL && primitiveClippedBottom)) &&
        !(widthsDownwards.empty() && widthsUpwards.empty()))
    {
        const tcu::IVec2 &range = (widthsDownwards.empty()) ? (widthsUpwards.front()) : (widthsDownwards.back());
        const bool squareFits   = (range.y() - range.x() + 1) >= expectedPointSize;
        const bool bboxClipped  = (pointType == POINT_PARTIAL) && (range.x() <= bbox.x() || range.y() >= bbox.z() - 1);
        const bool viewportClipped = range.x() <= 0 || range.y() >= viewport.getWidth() - 1;

        if (squareFits || bboxClipped || viewportClipped)
            return true;
    }

    // would square point would fit into the rasterized area

    for (int ndx = 0; ndx < (int)widthsUpwards.size(); ++ndx)
        widths.push_back(widthsUpwards[(int)widthsUpwards.size() - ndx - 1]);
    for (int ndx = 0; ndx < (int)widthsDownwards.size(); ++ndx)
        widths.push_back(widthsDownwards[ndx]);
    DE_ASSERT(!widths.empty());

    for (int y = 0; y < (int)widths.size() - expectedPointSize + 1; ++y)
    {
        tcu::IVec2 unionRange = widths[y];

        for (int dy = 1; dy < expectedPointSize; ++dy)
        {
            unionRange.x() = de::max(unionRange.x(), widths[y + dy].x());
            unionRange.y() = de::min(unionRange.y(), widths[y + dy].y());
        }

        // would a N x N block fit here?
        {
            const bool squareFits = (unionRange.y() - unionRange.x() + 1) >= expectedPointSize;
            const bool bboxClipped =
                (pointType == POINT_PARTIAL) && (unionRange.x() <= bbox.x() || unionRange.y() >= bbox.z() - 1);
            const bool viewportClipped = unionRange.x() <= 0 || unionRange.y() >= viewport.getWidth() - 1;

            if (squareFits || bboxClipped || viewportClipped)
                return true;
        }
    }

    if (--logFloodCounter >= 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Missing " << expectedPointSize << "x" << expectedPointSize
                           << " point near " << pointPos << ", vertex coordinates=" << refPoint.center.swizzle(0, 1)
                           << "." << tcu::TestLog::EndMessage;
    }
    return false;
}

tcu::IVec2 PointRenderCase::scanPointWidthAt(const tcu::IVec2 &pointPos, const tcu::Surface &viewport,
                                             int expectedPointSize, int componentNdx) const
{
    int minX = pointPos.x();
    int maxX = pointPos.x();

    // search horizontally for a point edges
    for (int x = pointPos.x() - 1; x >= 0; --x)
    {
        if (viewport.getPixel(x, pointPos.y()).toIVec()[componentNdx] == 0)
            break;

        // no need to go further than point width
        if (pointPos.x() - x > expectedPointSize)
            break;

        minX = x;
    }
    for (int x = pointPos.x() + 1; x < viewport.getWidth(); ++x)
    {
        if (viewport.getPixel(x, pointPos.y()).toIVec()[componentNdx] == 0)
            break;

        // no need to go further than point width
        if (x - pointPos.x() > expectedPointSize)
            break;

        maxX = x;
    }

    return tcu::IVec2(minX, maxX);
}

class BlitFboCase : public TestCase
{
public:
    enum RenderTarget
    {
        TARGET_DEFAULT = 0,
        TARGET_FBO,

        TARGET_LAST
    };

    BlitFboCase(Context &context, const char *name, const char *description, RenderTarget src, RenderTarget dst);
    ~BlitFboCase(void);

private:
    enum
    {
        FBO_SIZE = 256,
    };

    struct BlitArgs
    {
        tcu::IVec4 src;
        tcu::IVec4 dst;
        tcu::Vec4 bboxMin;
        tcu::Vec4 bboxMax;
        bool linear;
    };

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

    void fillSourceWithPattern(void);
    bool verifyImage(const BlitArgs &args);

    const RenderTarget m_src;
    const RenderTarget m_dst;

    std::vector<BlitArgs> m_iterations;
    int m_iteration;
    de::MovePtr<glu::Framebuffer> m_srcFbo;
    de::MovePtr<glu::Framebuffer> m_dstFbo;
    de::MovePtr<glu::Renderbuffer> m_srcRbo;
    de::MovePtr<glu::Renderbuffer> m_dstRbo;
    de::MovePtr<glu::ShaderProgram> m_program;
    de::MovePtr<glu::Buffer> m_vbo;
    glw::GLuint m_vao;
};

BlitFboCase::BlitFboCase(Context &context, const char *name, const char *description, RenderTarget src,
                         RenderTarget dst)
    : TestCase(context, name, description)
    , m_src(src)
    , m_dst(dst)
    , m_iteration(0)
    , m_vao(0)
{
    DE_ASSERT(src < TARGET_LAST);
    DE_ASSERT(dst < TARGET_LAST);
}

BlitFboCase::~BlitFboCase(void)
{
    deinit();
}

void BlitFboCase::init(void)
{
    const int numIterations          = 12;
    const bool defaultFBMultisampled = (m_context.getRenderTarget().getNumSamples() > 1);
    const glw::Functions &gl         = m_context.getRenderContext().getFunctions();
    de::Random rnd(0xABC123);

    m_testCtx.getLog() << tcu::TestLog::Message << "Using BlitFramebuffer to blit area from "
                       << ((m_src == TARGET_DEFAULT) ? ("default fb") : ("fbo")) << " to "
                       << ((m_dst == TARGET_DEFAULT) ? ("default fb") : ("fbo")) << ".\n"
                       << "Varying blit arguments and primitive bounding box between iterations.\n"
                       << "Expecting bounding box to have no effect on blitting.\n"
                       << "Source framebuffer is filled with green-yellow grid.\n"
                       << tcu::TestLog::EndMessage;

    if (!boundingBoxSupported(m_context))
        throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");

    if (m_dst == TARGET_DEFAULT && defaultFBMultisampled)
        throw tcu::NotSupportedError("Test requires non-multisampled default framebuffer");

    // resources

    if (m_src == TARGET_FBO)
    {
        m_srcRbo = de::MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(m_context.getRenderContext()));
        gl.bindRenderbuffer(GL_RENDERBUFFER, **m_srcRbo);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, FBO_SIZE, FBO_SIZE);
        GLU_EXPECT_NO_ERROR(gl.getError(), "src rbo");

        m_srcFbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
        gl.bindFramebuffer(GL_FRAMEBUFFER, **m_srcFbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **m_srcRbo);
        GLU_EXPECT_NO_ERROR(gl.getError(), "src fbo");
    }

    if (m_dst == TARGET_FBO)
    {
        m_dstRbo = de::MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(m_context.getRenderContext()));
        gl.bindRenderbuffer(GL_RENDERBUFFER, **m_dstRbo);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, FBO_SIZE, FBO_SIZE);
        GLU_EXPECT_NO_ERROR(gl.getError(), "dst rbo");

        m_dstFbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
        gl.bindFramebuffer(GL_FRAMEBUFFER, **m_dstFbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **m_dstRbo);
        GLU_EXPECT_NO_ERROR(gl.getError(), "dst fbo");
    }

    {
        const char *const vertexSource   = "${GLSL_VERSION_DECL}\n"
                                           "in highp vec4 a_position;\n"
                                           "out highp vec4 v_position;\n"
                                           "void main()\n"
                                           "{\n"
                                           "    gl_Position = a_position;\n"
                                           "    v_position = a_position;\n"
                                           "}\n";
        const char *const fragmentSource = "${GLSL_VERSION_DECL}\n"
                                           "in mediump vec4 v_position;\n"
                                           "layout(location=0) out mediump vec4 dEQP_FragColor;\n"
                                           "void main()\n"
                                           "{\n"
                                           "    const mediump vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
                                           "    const mediump vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
                                           "    dEQP_FragColor = (step(0.1, mod(v_position.x, 0.2)) == step(0.1, "
                                           "mod(v_position.y, 0.2))) ? (green) : (yellow);\n"
                                           "}\n";

        m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(
            m_context.getRenderContext(), glu::ProgramSources()
                                              << glu::VertexSource(specializeShader(m_context, vertexSource))
                                              << glu::FragmentSource(specializeShader(m_context, fragmentSource))));

        if (!m_program->isOk())
        {
            m_testCtx.getLog() << *m_program;
            throw tcu::TestError("failed to build program");
        }
    }

    // Generate VAO for desktop OpenGL
    if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
    {
        gl.genVertexArrays(1, &m_vao);
        gl.bindVertexArray(m_vao);
    }

    {
        static const tcu::Vec4 s_quadCoords[] = {
            tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
            tcu::Vec4(-1.0f, 1.0f, 0.0f, 1.0f),
            tcu::Vec4(1.0f, -1.0f, 0.0f, 1.0f),
            tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
        };

        m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));

        gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
        gl.bufferData(GL_ARRAY_BUFFER, sizeof(s_quadCoords), s_quadCoords, GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set buf");
    }

    // gen iterations

    {
        const tcu::IVec2 srcSize =
            (m_src == TARGET_DEFAULT) ?
                (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) :
                (tcu::IVec2(FBO_SIZE, FBO_SIZE));
        const tcu::IVec2 dstSize =
            (m_dst == TARGET_DEFAULT) ?
                (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) :
                (tcu::IVec2(FBO_SIZE, FBO_SIZE));

        m_testCtx.getLog() << tcu::TestLog::Message << "srcSize = " << srcSize << "\n"
                           << "dstSize = " << dstSize << "\n"
                           << tcu::TestLog::EndMessage;

        for (int ndx = 0; ndx < numIterations; ++ndx)
        {
            BlitArgs args;

            if (m_src == TARGET_DEFAULT && defaultFBMultisampled)
            {
                const tcu::IVec2 unionSize =
                    tcu::IVec2(de::min(srcSize.x(), dstSize.x()), de::min(srcSize.y(), dstSize.y()));
                const int srcWidth  = rnd.getInt(1, unionSize.x());
                const int srcHeight = rnd.getInt(1, unionSize.y());
                const int srcX      = rnd.getInt(0, unionSize.x() - srcWidth);
                const int srcY      = rnd.getInt(0, unionSize.y() - srcHeight);

                args.src.x() = srcX;
                args.src.y() = srcY;
                args.src.z() = srcX + srcWidth;
                args.src.w() = srcY + srcHeight;

                args.dst = args.src;
            }
            else
            {
                const int srcWidth  = rnd.getInt(1, srcSize.x());
                const int srcHeight = rnd.getInt(1, srcSize.y());
                const int srcX      = rnd.getInt(0, srcSize.x() - srcWidth);
                const int srcY      = rnd.getInt(0, srcSize.y() - srcHeight);
                const int dstWidth  = rnd.getInt(1, dstSize.x());
                const int dstHeight = rnd.getInt(1, dstSize.y());
                const int dstX =
                    rnd.getInt(-(dstWidth / 2), dstSize.x() - (dstWidth + 1) / 2); // allow dst go out of bounds
                const int dstY = rnd.getInt(-(dstHeight / 2), dstSize.y() - (dstHeight + 1) / 2);

                args.src.x() = srcX;
                args.src.y() = srcY;
                args.src.z() = srcX + srcWidth;
                args.src.w() = srcY + srcHeight;
                args.dst.x() = dstX;
                args.dst.y() = dstY;
                args.dst.z() = dstX + dstWidth;
                args.dst.w() = dstY + dstHeight;
            }

            args.bboxMin.x() = rnd.getFloat(-1.1f, 1.1f);
            args.bboxMin.y() = rnd.getFloat(-1.1f, 1.1f);
            args.bboxMin.z() = rnd.getFloat(-1.1f, 1.1f);
            args.bboxMin.w() = rnd.getFloat(0.9f, 1.1f);

            args.bboxMax.x() = rnd.getFloat(-1.1f, 1.1f);
            args.bboxMax.y() = rnd.getFloat(-1.1f, 1.1f);
            args.bboxMax.z() = rnd.getFloat(-1.1f, 1.1f);
            args.bboxMax.w() = rnd.getFloat(0.9f, 1.1f);

            if (args.bboxMin.x() / args.bboxMin.w() > args.bboxMax.x() / args.bboxMax.w())
                std::swap(args.bboxMin.x(), args.bboxMax.x());
            if (args.bboxMin.y() / args.bboxMin.w() > args.bboxMax.y() / args.bboxMax.w())
                std::swap(args.bboxMin.y(), args.bboxMax.y());
            if (args.bboxMin.z() / args.bboxMin.w() > args.bboxMax.z() / args.bboxMax.w())
                std::swap(args.bboxMin.z(), args.bboxMax.z());

            args.linear = rnd.getBool();

            m_iterations.push_back(args);
        }
    }
}

void BlitFboCase::deinit(void)
{
    m_srcFbo.clear();
    m_srcRbo.clear();
    m_dstFbo.clear();
    m_dstRbo.clear();
    m_program.clear();
    m_vbo.clear();

    if (m_vao)
    {
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
        m_vao = 0;
    }
}

BlitFboCase::IterateResult BlitFboCase::iterate(void)
{
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration),
                                        "Iteration " + de::toString(m_iteration + 1) + " / " +
                                            de::toString((int)m_iterations.size()));
    const BlitArgs &blitCfg  = m_iterations[m_iteration];
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    if (m_iteration == 0)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    // fill source with test pattern. Default fb must be filled for each iteration because contents might not survive the swap
    if (m_src == TARGET_DEFAULT || m_iteration == 0)
        fillSourceWithPattern();

    m_testCtx.getLog() << tcu::TestLog::Message << "Set bounding box:\n"
                       << "\tmin:" << blitCfg.bboxMin << "\n"
                       << "\tmax:" << blitCfg.bboxMax << "\n"
                       << "Blit:\n"
                       << "\tsrc: " << blitCfg.src << "\n"
                       << "\tdst: " << blitCfg.dst << "\n"
                       << "\tfilter: " << ((blitCfg.linear) ? ("linear") : ("nearest")) << tcu::TestLog::EndMessage;

    auto boundingBoxFunc = getBoundingBoxFunction(m_context);

    boundingBoxFunc(blitCfg.bboxMin.x(), blitCfg.bboxMin.y(), blitCfg.bboxMin.z(), blitCfg.bboxMin.w(),
                    blitCfg.bboxMax.x(), blitCfg.bboxMax.y(), blitCfg.bboxMax.z(), blitCfg.bboxMax.w());
    gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER,
                       (m_dst == TARGET_FBO) ? (**m_dstFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);

    gl.bindFramebuffer(GL_READ_FRAMEBUFFER,
                       (m_src == TARGET_FBO) ? (**m_srcFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
    gl.blitFramebuffer(blitCfg.src.x(), blitCfg.src.y(), blitCfg.src.z(), blitCfg.src.w(), blitCfg.dst.x(),
                       blitCfg.dst.y(), blitCfg.dst.z(), blitCfg.dst.w(), GL_COLOR_BUFFER_BIT,
                       ((blitCfg.linear) ? (GL_LINEAR) : (GL_NEAREST)));
    GLU_EXPECT_NO_ERROR(gl.getError(), "blit");

    if (!verifyImage(blitCfg))
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected blit result");

    return (++m_iteration == (int)m_iterations.size()) ? (STOP) : (CONTINUE);
}

bool BlitFboCase::verifyImage(const BlitArgs &args)
{
    const int colorThreshold = 4; //!< this test case is not about how color is preserved, allow almost anything
    const tcu::IVec2 dstSize =
        (m_dst == TARGET_DEFAULT) ?
            (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) :
            (tcu::IVec2(FBO_SIZE, FBO_SIZE));
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::Surface viewport(dstSize.x(), dstSize.y());
    tcu::Surface errorMask(dstSize.x(), dstSize.y());
    bool anyError = false;

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying blit result" << tcu::TestLog::EndMessage;

    gl.bindFramebuffer(GL_READ_FRAMEBUFFER,
                       (m_dst == TARGET_FBO) ? (**m_dstFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
    glu::readPixels(m_context.getRenderContext(), 0, 0, viewport.getAccess());

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

    for (int y = 0; y < dstSize.y(); ++y)
        for (int x = 0; x < dstSize.x(); ++x)
        {
            const tcu::RGBA color = viewport.getPixel(x, y);
            const bool inside     = (x >= args.dst.x() && x < args.dst.z() && y >= args.dst.y() && y < args.dst.w());
            const bool error      = (inside) ?
                                        (color.getGreen() < 255 - colorThreshold || color.getBlue() > colorThreshold) :
                                        (color.getRed() > colorThreshold || color.getGreen() > colorThreshold ||
                                    color.getBlue() > colorThreshold);

            if (error)
            {
                anyError = true;
                errorMask.setPixel(x, y, tcu::RGBA::red());
            }
        }

    if (anyError)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
                           << tcu::TestLog::EndImageSet;
        return false;
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Result image ok." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
                           << tcu::TestLog::EndImageSet;
        return true;
    }
}

void BlitFboCase::fillSourceWithPattern(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const tcu::IVec2 srcSize =
        (m_src == TARGET_DEFAULT) ?
            (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) :
            (tcu::IVec2(FBO_SIZE, FBO_SIZE));
    const int posLocation = gl.getAttribLocation(m_program->getProgram(), "a_position");

    gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER,
                       (m_src == TARGET_FBO) ? (**m_srcFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
    gl.viewport(0, 0, srcSize.x(), srcSize.y());
    gl.useProgram(m_program->getProgram());

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

    gl.enableVertexAttribArray(posLocation);
    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 4 * (int)sizeof(float), NULL);
    gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

class DepthDrawCase : public TestCase
{
public:
    enum DepthType
    {
        DEPTH_BUILTIN = 0,
        DEPTH_USER_DEFINED,

        DEPTH_LAST
    };
    enum BBoxState
    {
        STATE_GLOBAL = 0,
        STATE_PER_PRIMITIVE,

        STATE_LAST
    };
    enum BBoxSize
    {
        BBOX_EQUAL = 0,
        BBOX_LARGER,

        BBOX_LAST
    };

    DepthDrawCase(Context &context, const char *name, const char *description, DepthType depthType, BBoxState state,
                  BBoxSize bboxSize);
    ~DepthDrawCase(void);

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

    std::string genVertexSource(void) const;
    std::string genFragmentSource(void) const;
    std::string genTessellationControlSource(void) const;
    std::string genTessellationEvaluationSource(void) const;
    void generateAttributeData(std::vector<tcu::Vec4> &data) const;
    bool verifyImage(const tcu::Surface &viewport) const;

    enum
    {
        RENDER_AREA_SIZE = 256,
    };

    struct LayerInfo
    {
        float zOffset;
        float zScale;
        tcu::Vec4 color1;
        tcu::Vec4 color2;
    };

    const int m_numLayers;
    const int m_gridSize;

    const DepthType m_depthType;
    const BBoxState m_state;
    const BBoxSize m_bboxSize;

    de::MovePtr<glu::ShaderProgram> m_program;
    de::MovePtr<glu::Buffer> m_vbo;
    glw::GLuint m_vao;
    std::vector<LayerInfo> m_layers;
};

DepthDrawCase::DepthDrawCase(Context &context, const char *name, const char *description, DepthType depthType,
                             BBoxState state, BBoxSize bboxSize)
    : TestCase(context, name, description)
    , m_numLayers(14)
    , m_gridSize(24)
    , m_depthType(depthType)
    , m_state(state)
    , m_bboxSize(bboxSize)
    , m_vao(0)
{
    DE_ASSERT(depthType < DEPTH_LAST);
    DE_ASSERT(state < STATE_LAST);
    DE_ASSERT(bboxSize < BBOX_LAST);
}

DepthDrawCase::~DepthDrawCase(void)
{
    deinit();
}

void DepthDrawCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const bool hasES32OrGL45 = supportsES32OrGL45(m_context);

    // requirements
    if (!boundingBoxSupported(m_context))
        throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
    if (m_state == STATE_PER_PRIMITIVE && !hasES32OrGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
    if (m_context.getRenderTarget().getDepthBits() == 0)
        throw tcu::NotSupportedError("Test requires depth buffer");
    if (m_context.getRenderTarget().getWidth() < RENDER_AREA_SIZE ||
        m_context.getRenderTarget().getHeight() < RENDER_AREA_SIZE)
        throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_AREA_SIZE) + "x" +
                                     de::toString<int>(RENDER_AREA_SIZE) + " viewport");

    // log
    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Rendering multiple triangle grids with with different z coordinates.\n"
                       << "Topmost grid is green-yellow, other grids are blue-red.\n"
                       << "Expecting only the green-yellow grid to be visible.\n"
                       << "Setting primitive bounding box "
                       << ((m_bboxSize == BBOX_EQUAL) ? ("to exactly cover") : ("to cover"))
                       << ((m_state == STATE_GLOBAL) ? (" each grid") : (" each triangle"))
                       << ((m_bboxSize == BBOX_EQUAL) ? (".") : (" and include some padding.")) << "\n"
                       << "Set bounding box using "
                       << ((m_state == STATE_GLOBAL) ? ("PRIMITIVE_BOUNDING_BOX_EXT state") :
                                                       ("gl_BoundingBoxEXT output"))
                       << "\n"
                       << ((m_depthType == DEPTH_USER_DEFINED) ? ("Fragment depth is set in the fragment shader") :
                                                                 (""))
                       << tcu::TestLog::EndMessage;

    // resources

    {
        glu::ProgramSources sources;
        sources << glu::VertexSource(specializeShader(m_context, genVertexSource().c_str()));
        sources << glu::FragmentSource(specializeShader(m_context, genFragmentSource().c_str()));

        if (m_state == STATE_PER_PRIMITIVE)
            sources << glu::TessellationControlSource(
                           specializeShader(m_context, genTessellationControlSource().c_str()))
                    << glu::TessellationEvaluationSource(
                           specializeShader(m_context, genTessellationEvaluationSource().c_str()));

        m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources));
        GLU_EXPECT_NO_ERROR(gl.getError(), "build program");

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "ShaderProgram", "Shader program");
            m_testCtx.getLog() << *m_program;
        }

        if (!m_program->isOk())
            throw tcu::TestError("failed to build program");
    }

    // Generate VAO for desktop OpenGL
    if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
    {
        gl.genVertexArrays(1, &m_vao);
        gl.bindVertexArray(m_vao);
    }

    {
        std::vector<tcu::Vec4> data;

        generateAttributeData(data);

        m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));
        gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
        gl.bufferData(GL_ARRAY_BUFFER, (int)(sizeof(tcu::Vec4) * data.size()), &data[0], GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "buf upload");
    }

    // gen layers
    {
        de::Random rnd(0x12345);

        m_layers.resize(m_numLayers);
        for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
        {
            m_layers[layerNdx].zOffset = ((float)layerNdx / (float)m_numLayers) * 2.0f - 1.0f;
            m_layers[layerNdx].zScale  = (2.0f / (float)m_numLayers);
            m_layers[layerNdx].color1 =
                (layerNdx == 0) ? (tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f)) : (tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
            m_layers[layerNdx].color2 =
                (layerNdx == 0) ? (tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f)) : (tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f));
        }
        rnd.shuffle(m_layers.begin(), m_layers.end());
    }
}

void DepthDrawCase::deinit(void)
{
    m_program.clear();
    m_vbo.clear();

    if (m_vao)
    {
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
        m_vao = 0;
    }
}

DepthDrawCase::IterateResult DepthDrawCase::iterate(void)
{
    const bool hasTessellation          = (m_state == STATE_PER_PRIMITIVE);
    const glw::Functions &gl            = m_context.getRenderContext().getFunctions();
    const glw::GLint posLocation        = gl.getAttribLocation(m_program->getProgram(), "a_position");
    const glw::GLint colLocation        = gl.getAttribLocation(m_program->getProgram(), "a_colorMix");
    const glw::GLint depthBiasLocation  = gl.getUniformLocation(m_program->getProgram(), "u_depthBias");
    const glw::GLint depthScaleLocation = gl.getUniformLocation(m_program->getProgram(), "u_depthScale");
    const glw::GLint color1Location     = gl.getUniformLocation(m_program->getProgram(), "u_color1");
    const glw::GLint color2Location     = gl.getUniformLocation(m_program->getProgram(), "u_color2");

    tcu::Surface viewport(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
    de::Random rnd(0x213237);

    TCU_CHECK(posLocation != -1);
    TCU_CHECK(colLocation != -1);
    TCU_CHECK(depthBiasLocation != -1);
    TCU_CHECK(depthScaleLocation != -1);
    TCU_CHECK(color1Location != -1);
    TCU_CHECK(color2Location != -1);

    gl.viewport(0, 0, RENDER_AREA_SIZE, RENDER_AREA_SIZE);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clearDepthf(1.0f);
    gl.depthFunc(GL_LESS);
    gl.enable(GL_DEPTH_TEST);
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "setup viewport");

    auto boundingBoxFunc = getBoundingBoxFunction(m_context);

    gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, (int)(8 * sizeof(float)),
                           glu::BufferOffsetAsPointer(0 * sizeof(float)));
    gl.vertexAttribPointer(colLocation, 4, GL_FLOAT, GL_FALSE, (int)(8 * sizeof(float)),
                           glu::BufferOffsetAsPointer(4 * sizeof(float)));
    gl.enableVertexAttribArray(posLocation);
    gl.enableVertexAttribArray(colLocation);
    gl.useProgram(m_program->getProgram());
    GLU_EXPECT_NO_ERROR(gl.getError(), "setup va");

    if (hasTessellation)
        gl.patchParameteri(GL_PATCH_VERTICES, 3);

    for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
    {
        gl.uniform1f(depthBiasLocation, m_layers[layerNdx].zOffset);
        gl.uniform1f(depthScaleLocation, m_layers[layerNdx].zScale);
        gl.uniform4fv(color1Location, 1, m_layers[layerNdx].color1.getPtr());
        gl.uniform4fv(color2Location, 1, m_layers[layerNdx].color2.getPtr());

        if (m_state == STATE_GLOBAL)
        {
            const float negPadding = (m_bboxSize == BBOX_EQUAL) ? (0.0f) : (rnd.getFloat() * 0.3f);
            const float posPadding = (m_bboxSize == BBOX_EQUAL) ? (0.0f) : (rnd.getFloat() * 0.3f);

            boundingBoxFunc(-1.0f, -1.0f, m_layers[layerNdx].zOffset - negPadding, 1.0f, 1.0f, 1.0f,
                            (m_layers[layerNdx].zOffset + m_layers[layerNdx].zScale + posPadding), 1.0f);
        }

        gl.drawArrays((hasTessellation) ? (GL_PATCHES) : (GL_TRIANGLES), 0, m_gridSize * m_gridSize * 6);
    }

    glu::readPixels(m_context.getRenderContext(), 0, 0, viewport.getAccess());
    GLU_EXPECT_NO_ERROR(gl.getError(), "render and read");

    if (verifyImage(viewport))
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");

    return STOP;
}

std::string DepthDrawCase::genVertexSource(void) const
{
    const bool hasTessellation = (m_state == STATE_PER_PRIMITIVE);
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in highp vec4 a_position;\n"
           "in highp vec4 a_colorMix;\n"
           "out highp vec4 vtx_colorMix;\n";

    if (!hasTessellation && m_depthType == DEPTH_USER_DEFINED)
        buf << "out highp float v_fragDepth;\n";

    if (!hasTessellation)
        buf << "uniform highp float u_depthBias;\n"
               "uniform highp float u_depthScale;\n";

    buf << "\n"
           "void main()\n"
           "{\n";

    if (hasTessellation)
        buf << "    gl_Position = a_position;\n";
    else if (m_depthType == DEPTH_USER_DEFINED)
        buf << "    highp float unusedZ = a_position.z;\n"
               "    highp float writtenZ = a_position.w;\n"
               "    gl_Position = vec4(a_position.xy, unusedZ, 1.0);\n"
               "    v_fragDepth = writtenZ * u_depthScale + u_depthBias;\n";
    else
        buf << "    highp float writtenZ = a_position.w;\n"
               "    gl_Position = vec4(a_position.xy, writtenZ * u_depthScale + u_depthBias, 1.0);\n";

    buf << "    vtx_colorMix = a_colorMix;\n"
           "}\n";

    return buf.str();
}

std::string DepthDrawCase::genFragmentSource(void) const
{
    const bool hasTessellation     = (m_state == STATE_PER_PRIMITIVE);
    const char *const colorMixName = (hasTessellation) ? ("tess_eval_colorMix") : ("vtx_colorMix");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in mediump vec4 "
        << colorMixName << ";\n";

    if (m_depthType == DEPTH_USER_DEFINED)
        buf << "in mediump float v_fragDepth;\n";

    buf << "layout(location = 0) out mediump vec4 o_color;\n"
           "uniform highp vec4 u_color1;\n"
           "uniform highp vec4 u_color2;\n"
           "\n"
           "void main()\n"
           "{\n"
           "    o_color = mix(u_color1, u_color2, "
        << colorMixName << ");\n";

    if (m_depthType == DEPTH_USER_DEFINED)
        buf << "    gl_FragDepth = v_fragDepth * 0.5 + 0.5;\n";

    buf << "}\n";

    return buf.str();
}

std::string DepthDrawCase::genTessellationControlSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${ARB_ES32_COMPATIBILITY_REQUIRE}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${PRIMITIVE_BOUNDING_BOX_REQUIRE}\n"
           "layout(vertices=3) out;\n"
           "\n"
           "uniform highp float u_depthBias;\n"
           "uniform highp float u_depthScale;\n"
           "\n"
           "in highp vec4 vtx_colorMix[];\n"
           "out highp vec4 tess_ctrl_colorMix[];\n"
           "\n"
           "void main()\n"
           "{\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
           "    tess_ctrl_colorMix[gl_InvocationID] = vtx_colorMix[0];\n"
           "\n"
           "    gl_TessLevelOuter[0] = 2.8;\n"
           "    gl_TessLevelOuter[1] = 2.8;\n"
           "    gl_TessLevelOuter[2] = 2.8;\n"
           "    gl_TessLevelInner[0] = 2.8;\n"
           "\n"
           "    // real Z stored in w component\n"
           "    highp vec4 minBound = vec4(min(min(vec3(gl_in[0].gl_Position.xy, gl_in[0].gl_Position.w * u_depthScale "
           "+ u_depthBias),\n"
           "                                       vec3(gl_in[1].gl_Position.xy, gl_in[1].gl_Position.w * u_depthScale "
           "+ u_depthBias)),\n"
           "                                   vec3(gl_in[2].gl_Position.xy, gl_in[2].gl_Position.w * u_depthScale + "
           "u_depthBias)), 1.0);\n"
           "    highp vec4 maxBound = vec4(max(max(vec3(gl_in[0].gl_Position.xy, gl_in[0].gl_Position.w * u_depthScale "
           "+ u_depthBias),\n"
           "                                       vec3(gl_in[1].gl_Position.xy, gl_in[1].gl_Position.w * u_depthScale "
           "+ u_depthBias)),\n"
           "                                   vec3(gl_in[2].gl_Position.xy, gl_in[2].gl_Position.w * u_depthScale + "
           "u_depthBias)), 1.0);\n";

    if (m_bboxSize == BBOX_EQUAL)
        buf << "    ${PRIM_GL_BOUNDING_BOX}[0] = minBound;\n"
               "    ${PRIM_GL_BOUNDING_BOX}[1] = maxBound;\n";
    else
        buf << "    highp float nedPadding = mod(gl_in[0].gl_Position.z, 0.3);\n"
               "    highp float posPadding = mod(gl_in[1].gl_Position.z, 0.3);\n"
               "    ${PRIM_GL_BOUNDING_BOX}[0] = minBound - vec4(0.0, 0.0, nedPadding, 0.0);\n"
               "    ${PRIM_GL_BOUNDING_BOX}[1] = maxBound + vec4(0.0, 0.0, posPadding, 0.0);\n";

    buf << "}\n";

    return buf.str();
}

std::string DepthDrawCase::genTessellationEvaluationSource(void) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "${GPU_SHADER5_REQUIRE}\n"
           "layout(triangles) in;\n"
           "\n"
           "in highp vec4 tess_ctrl_colorMix[];\n"
           "out highp vec4 tess_eval_colorMix;\n";

    if (m_depthType == DEPTH_USER_DEFINED)
        buf << "out highp float v_fragDepth;\n";

    buf << "uniform highp float u_depthBias;\n"
           "uniform highp float u_depthScale;\n"
           "\n"
           "precise gl_Position;\n"
           "\n"
           "void main()\n"
           "{\n"
           "    highp vec4 tessellatedPos = gl_TessCoord.x * gl_in[0].gl_Position + gl_TessCoord.y * "
           "gl_in[1].gl_Position + gl_TessCoord.z * gl_in[2].gl_Position;\n";

    if (m_depthType == DEPTH_USER_DEFINED)
        buf << "    highp float unusedZ = tessellatedPos.z;\n"
               "    highp float writtenZ = tessellatedPos.w;\n"
               "    gl_Position = vec4(tessellatedPos.xy, unusedZ, 1.0);\n"
               "    v_fragDepth = writtenZ * u_depthScale + u_depthBias;\n";
    else
        buf << "    highp float writtenZ = tessellatedPos.w;\n"
               "    gl_Position = vec4(tessellatedPos.xy, writtenZ * u_depthScale + u_depthBias, 1.0);\n";

    buf << "    tess_eval_colorMix = tess_ctrl_colorMix[0];\n"
           "}\n";

    return buf.str();
}

void DepthDrawCase::generateAttributeData(std::vector<tcu::Vec4> &data) const
{
    const tcu::Vec4 color1(0.0f, 0.0f, 0.0f, 0.0f); // mix weights
    const tcu::Vec4 color2(1.0f, 1.0f, 1.0f, 1.0f);
    std::vector<int> cellOrder(m_gridSize * m_gridSize);
    de::Random rnd(0xAB54321);

    // generate grid with cells in random order
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
        cellOrder[ndx] = ndx;
    rnd.shuffle(cellOrder.begin(), cellOrder.end());

    data.resize(m_gridSize * m_gridSize * 6 * 2);
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
    {
        const int cellNdx          = cellOrder[ndx];
        const int cellX            = cellNdx % m_gridSize;
        const int cellY            = cellNdx / m_gridSize;
        const tcu::Vec4 &cellColor = ((cellX + cellY) % 2 == 0) ? (color1) : (color2);

        data[ndx * 6 * 2 + 0]  = tcu::Vec4(float(cellX + 0) / float(m_gridSize) * 2.0f - 1.0f,
                                           float(cellY + 0) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);
        data[ndx * 6 * 2 + 1]  = cellColor;
        data[ndx * 6 * 2 + 2]  = tcu::Vec4(float(cellX + 1) / float(m_gridSize) * 2.0f - 1.0f,
                                           float(cellY + 1) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);
        data[ndx * 6 * 2 + 3]  = cellColor;
        data[ndx * 6 * 2 + 4]  = tcu::Vec4(float(cellX + 0) / float(m_gridSize) * 2.0f - 1.0f,
                                           float(cellY + 1) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);
        data[ndx * 6 * 2 + 5]  = cellColor;
        data[ndx * 6 * 2 + 6]  = tcu::Vec4(float(cellX + 0) / float(m_gridSize) * 2.0f - 1.0f,
                                           float(cellY + 0) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);
        data[ndx * 6 * 2 + 7]  = cellColor;
        data[ndx * 6 * 2 + 8]  = tcu::Vec4(float(cellX + 1) / float(m_gridSize) * 2.0f - 1.0f,
                                           float(cellY + 0) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);
        data[ndx * 6 * 2 + 9]  = cellColor;
        data[ndx * 6 * 2 + 10] = tcu::Vec4(float(cellX + 1) / float(m_gridSize) * 2.0f - 1.0f,
                                           float(cellY + 1) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);
        data[ndx * 6 * 2 + 11] = cellColor;

        // Fill Z with random values (fake Z)
        for (int vtxNdx = 0; vtxNdx < 6; ++vtxNdx)
            data[ndx * 6 * 2 + 2 * vtxNdx].z() = rnd.getFloat(0.0f, 1.0);

        // Fill W with other random values (written Z)
        for (int vtxNdx = 0; vtxNdx < 6; ++vtxNdx)
            data[ndx * 6 * 2 + 2 * vtxNdx].w() = rnd.getFloat(0.0f, 1.0);
    }
}

bool DepthDrawCase::verifyImage(const tcu::Surface &viewport) const
{
    tcu::Surface errorMask(viewport.getWidth(), viewport.getHeight());
    bool anyError = false;

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

    for (int y = 0; y < viewport.getHeight(); ++y)
        for (int x = 0; x < viewport.getWidth(); ++x)
        {
            const tcu::RGBA pixel = viewport.getPixel(x, y);
            bool error            = false;

            // expect green, yellow or a combination of these
            if (pixel.getGreen() != 255 || pixel.getBlue() != 0)
                error = true;

            if (error)
            {
                errorMask.setPixel(x, y, tcu::RGBA::red());
                anyError = true;
            }
        }

    if (anyError)
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
                           << tcu::TestLog::EndImageSet;
    else
        m_testCtx.getLog() << tcu::TestLog::Message << "Result image ok." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
                           << tcu::TestLog::EndImageSet;

    return !anyError;
}

class ClearCase : public TestCase
{
public:
    enum
    {
        SCISSOR_CLEAR_BIT      = 1 << 0,
        DRAW_TRIANGLE_BIT      = 1 << 1,
        PER_PRIMITIVE_BBOX_BIT = 1 << 2,
        FULLSCREEN_SCISSOR_BIT = 1 << 3,
    };

    ClearCase(Context &context, const char *name, const char *description, uint32_t flags);
    ~ClearCase(void);

private:
    struct DrawObject
    {
        int firstNdx;
        int numVertices;
    };

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

    void createVbo(void);
    void createProgram(void);
    void renderTo(tcu::Surface &dst, bool useBBox);
    bool verifyImagesEqual(const tcu::PixelBufferAccess &withoutBBox, const tcu::PixelBufferAccess &withBBox);
    bool verifyImageResultValid(const tcu::PixelBufferAccess &result);

    std::string genVertexSource(void) const;
    std::string genFragmentSource(void) const;
    std::string genTessellationControlSource(bool setBBox) const;
    std::string genTessellationEvaluationSource(void) const;

    const bool m_scissoredClear;
    const bool m_fullscreenScissor;
    const bool m_drawTriangles;
    const bool m_useGlobalState;

    de::MovePtr<glu::Buffer> m_vbo;
    de::MovePtr<glu::ShaderProgram> m_perPrimitiveProgram;
    de::MovePtr<glu::ShaderProgram> m_basicProgram;
    std::vector<DrawObject> m_drawObjects;
    std::vector<tcu::Vec4> m_objectVertices;
};

ClearCase::ClearCase(Context &context, const char *name, const char *description, uint32_t flags)
    : TestCase(context, name, description)
    , m_scissoredClear((flags & SCISSOR_CLEAR_BIT) != 0)
    , m_fullscreenScissor((flags & FULLSCREEN_SCISSOR_BIT) != 0)
    , m_drawTriangles((flags & DRAW_TRIANGLE_BIT) != 0)
    , m_useGlobalState((flags & PER_PRIMITIVE_BBOX_BIT) == 0)
{
    DE_ASSERT(m_useGlobalState || m_drawTriangles);      // per-triangle bbox requires triangles
    DE_ASSERT(!m_fullscreenScissor || m_scissoredClear); // fullscreenScissor requires scissoredClear
}

ClearCase::~ClearCase(void)
{
    deinit();
}

void ClearCase::init(void)
{
    const bool hasES32OrGL45 = supportsES32OrGL45(m_context);

    if (!boundingBoxSupported(m_context))
        throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
    if (m_drawTriangles && !hasES32OrGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");

    m_testCtx.getLog() << tcu::TestLog::Message << "Doing multiple" << ((m_scissoredClear) ? (" scissored") : (""))
                       << " color buffer clears"
                       << ((m_drawTriangles) ? (" and drawing some geometry between them") : ("")) << ".\n"
                       << ((m_scissoredClear && m_fullscreenScissor) ?
                               ("Setting scissor area to cover entire viewport.\n") :
                               (""))
                       << "Rendering with and without setting the bounding box.\n"
                       << "Expecting bounding box to have no effect on clears (i.e. results are constant).\n"
                       << "Set bounding box using "
                       << ((m_useGlobalState) ? ("PRIMITIVE_BOUNDING_BOX_EXT state") : ("gl_BoundingBoxEXT output"))
                       << ".\n"
                       << "Clear color is green with yellowish shades.\n"
                       << ((m_drawTriangles) ? ("Primitive color is yellow with greenish shades.\n") : (""))
                       << tcu::TestLog::EndMessage;

    if (m_drawTriangles)
    {
        createVbo();
        createProgram();
    }
}

void ClearCase::deinit(void)
{
    m_vbo.clear();
    m_perPrimitiveProgram.clear();
    m_basicProgram.clear();
    m_drawObjects    = std::vector<DrawObject>();
    m_objectVertices = std::vector<tcu::Vec4>();
}

ClearCase::IterateResult ClearCase::iterate(void)
{
    const tcu::IVec2 renderTargetSize(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
    tcu::Surface resultWithoutBBox(renderTargetSize.x(), renderTargetSize.y());
    tcu::Surface resultWithBBox(renderTargetSize.x(), renderTargetSize.y());

    // render with and without bbox set
    for (int passNdx = 0; passNdx < 2; ++passNdx)
    {
        const bool useBBox        = (passNdx == 1);
        tcu::Surface &destination = (useBBox) ? (resultWithBBox) : (resultWithoutBBox);

        renderTo(destination, useBBox);
    }

    // Verify images are equal and that the image does not contain (trivially detectable) garbage

    if (!verifyImagesEqual(resultWithoutBBox.getAccess(), resultWithBBox.getAccess()))
    {
        // verifyImagesEqual will print out the image and error mask
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
    }
    else if (!verifyImageResultValid(resultWithBBox.getAccess()))
    {
        // verifyImageResultValid will print out the image and error mask
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image comparison passed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Result", "Result", resultWithBBox.getAccess())
                           << tcu::TestLog::EndImageSet;

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

    return STOP;
}

void ClearCase::createVbo(void)
{
    const int numObjects     = 16;
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    de::Random rnd(deStringHash(getName()));

    m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));

    for (int objectNdx = 0; objectNdx < numObjects; ++objectNdx)
    {
        const int numTriangles = rnd.getInt(1, 4);
        const float minX       = rnd.getFloat(-1.2f, 0.8f);
        const float minY       = rnd.getFloat(-1.2f, 0.8f);
        const float maxX       = minX + rnd.getFloat(0.2f, 1.0f);
        const float maxY       = minY + rnd.getFloat(0.2f, 1.0f);

        DrawObject drawObject;
        drawObject.firstNdx    = (int)m_objectVertices.size();
        drawObject.numVertices = numTriangles * 3;

        m_drawObjects.push_back(drawObject);

        for (int triangleNdx = 0; triangleNdx < numTriangles; ++triangleNdx)
            for (int vertexNdx = 0; vertexNdx < 3; ++vertexNdx)
            {
                const float posX = rnd.getFloat(minX, maxX);
                const float posY = rnd.getFloat(minY, maxY);
                const float posZ = rnd.getFloat(-0.7f, 0.7f);
                const float posW = rnd.getFloat(0.9f, 1.1f);

                m_objectVertices.push_back(tcu::Vec4(posX, posY, posZ, posW));
            }
    }

    gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
    gl.bufferData(GL_ARRAY_BUFFER, (int)(m_objectVertices.size() * sizeof(tcu::Vec4)), &m_objectVertices[0],
                  GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "buffer upload");
}

void ClearCase::createProgram(void)
{
    m_basicProgram = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, genVertexSource().c_str()))
                              << glu::FragmentSource(specializeShader(m_context, genFragmentSource().c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, genTessellationControlSource(false).c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, genTessellationEvaluationSource().c_str()))));

    m_testCtx.getLog() << tcu::TestLog::Section("Program", "Shader program") << *m_basicProgram
                       << tcu::TestLog::EndSection;

    if (!m_basicProgram->isOk())
        throw tcu::TestError("shader build failed");

    if (!m_useGlobalState)
    {
        m_perPrimitiveProgram = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(
            m_context.getRenderContext(),
            glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, genVertexSource().c_str()))
                                  << glu::FragmentSource(specializeShader(m_context, genFragmentSource().c_str()))
                                  << glu::TessellationControlSource(
                                         specializeShader(m_context, genTessellationControlSource(true).c_str()))
                                  << glu::TessellationEvaluationSource(
                                         specializeShader(m_context, genTessellationEvaluationSource().c_str()))));

        m_testCtx.getLog() << tcu::TestLog::Section("PerPrimitiveProgram", "Shader program that sets the bounding box")
                           << *m_perPrimitiveProgram << tcu::TestLog::EndSection;

        if (!m_perPrimitiveProgram->isOk())
            throw tcu::TestError("shader build failed");
    }
}

void ClearCase::renderTo(tcu::Surface &dst, bool useBBox)
{
    const int numOps = 45;
    const tcu::Vec4 yellow(1.0f, 1.0f, 0.0f, 1.0f);
    const tcu::Vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
    const tcu::IVec2 renderTargetSize(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    de::Random rnd(deStringHash(getName()));
    glu::VertexArray vao(m_context.getRenderContext());

    // always do the initial clear
    gl.disable(GL_SCISSOR_TEST);
    gl.viewport(0, 0, renderTargetSize.x(), renderTargetSize.y());
    gl.clearColor(yellow.x(), yellow.y(), yellow.z(), yellow.w());
    gl.clear(GL_COLOR_BUFFER_BIT);
    gl.finish();

    // prepare draw
    if (m_scissoredClear)
        gl.enable(GL_SCISSOR_TEST);

    if (m_drawTriangles)
    {
        const uint32_t programHandle =
            (m_useGlobalState || !useBBox) ? (m_basicProgram->getProgram()) : (m_perPrimitiveProgram->getProgram());
        const int positionAttribLoc = gl.getAttribLocation(programHandle, "a_position");

        TCU_CHECK(positionAttribLoc != -1);

        gl.useProgram(programHandle);
        gl.bindVertexArray(*vao);
        gl.enableVertexAttribArray(positionAttribLoc);
        gl.vertexAttribPointer(positionAttribLoc, 4, GL_FLOAT, GL_FALSE, (int)sizeof(tcu::Vec4), DE_NULL);
        gl.patchParameteri(GL_PATCH_VERTICES, 3);
    }

    // do random scissor/clearldraw operations
    for (int opNdx = 0; opNdx < numOps; ++opNdx)
    {
        const int drawObjNdx           = (m_drawTriangles) ? (rnd.getInt(0, (int)m_drawObjects.size() - 1)) : (0);
        const int objectVertexStartNdx = (m_drawTriangles) ? (m_drawObjects[drawObjNdx].firstNdx) : (0);
        const int objectVertexLength   = (m_drawTriangles) ? (m_drawObjects[drawObjNdx].numVertices) : (0);
        tcu::Vec4 bboxMin;
        tcu::Vec4 bboxMax;

        if (m_drawTriangles)
        {
            bboxMin = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
            bboxMax = tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f);

            // calc bbox
            for (int vertexNdx = objectVertexStartNdx; vertexNdx < objectVertexStartNdx + objectVertexLength;
                 ++vertexNdx)
                for (int componentNdx = 0; componentNdx < 4; ++componentNdx)
                {
                    bboxMin[componentNdx] = de::min(bboxMin[componentNdx], m_objectVertices[vertexNdx][componentNdx]);
                    bboxMax[componentNdx] = de::max(bboxMax[componentNdx], m_objectVertices[vertexNdx][componentNdx]);
                }
        }
        else
        {
            // no geometry, just random something
            bboxMin.x() = rnd.getFloat(-1.2f, 1.0f);
            bboxMin.y() = rnd.getFloat(-1.2f, 1.0f);
            bboxMin.z() = rnd.getFloat(-1.2f, 1.0f);
            bboxMin.w() = 1.0f;
            bboxMax.x() = bboxMin.x() + rnd.getFloat(0.2f, 1.0f);
            bboxMax.y() = bboxMin.y() + rnd.getFloat(0.2f, 1.0f);
            bboxMax.z() = bboxMin.z() + rnd.getFloat(0.2f, 1.0f);
            bboxMax.w() = 1.0f;
        }

        if (m_scissoredClear)
        {
            const int scissorX = (m_fullscreenScissor) ? (0) : rnd.getInt(0, renderTargetSize.x() - 1);
            const int scissorY = (m_fullscreenScissor) ? (0) : rnd.getInt(0, renderTargetSize.y() - 1);
            const int scissorW =
                (m_fullscreenScissor) ? (renderTargetSize.x()) : rnd.getInt(0, renderTargetSize.x() - scissorX);
            const int scissorH =
                (m_fullscreenScissor) ? (renderTargetSize.y()) : rnd.getInt(0, renderTargetSize.y() - scissorY);

            gl.scissor(scissorX, scissorY, scissorW, scissorH);
        }

        {
            const tcu::Vec4 color = tcu::mix(green, yellow, rnd.getFloat() * 0.4f); // greenish
            gl.clearColor(color.x(), color.y(), color.z(), color.w());
            gl.clear(GL_COLOR_BUFFER_BIT);
        }

        if (useBBox)
        {
            DE_ASSERT(m_useGlobalState || m_drawTriangles); // !m_useGlobalState -> m_drawTriangles

            auto boundingBoxFunc = getBoundingBoxFunction(m_context);
            if (m_useGlobalState)
                boundingBoxFunc(bboxMin.x(), bboxMin.y(), bboxMin.z(), bboxMin.w(), bboxMax.x(), bboxMax.y(),
                                bboxMax.z(), bboxMax.w());
        }

        if (m_drawTriangles)
            gl.drawArrays(GL_PATCHES, objectVertexStartNdx, objectVertexLength);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "post draw");
    glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

bool ClearCase::verifyImagesEqual(const tcu::PixelBufferAccess &withoutBBox, const tcu::PixelBufferAccess &withBBox)
{
    DE_ASSERT(withoutBBox.getWidth() == withBBox.getWidth());
    DE_ASSERT(withoutBBox.getHeight() == withBBox.getHeight());

    tcu::Surface errorMask(withoutBBox.getWidth(), withoutBBox.getHeight());
    bool anyError = false;

    tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toIVec());

    for (int y = 0; y < withoutBBox.getHeight(); ++y)
        for (int x = 0; x < withoutBBox.getWidth(); ++x)
        {
            if (withoutBBox.getPixelInt(x, y) != withBBox.getPixelInt(x, y))
            {
                errorMask.setPixel(x, y, tcu::RGBA::red());
                anyError = true;
            }
        }

    if (anyError)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image comparison failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image comparison")
                           << tcu::TestLog::Image("WithoutBBox", "Result with bounding box not set", withoutBBox)
                           << tcu::TestLog::Image("WithBBox", "Result with bounding box set", withBBox)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
                           << tcu::TestLog::EndImageSet;
    }

    return !anyError;
}

bool ClearCase::verifyImageResultValid(const tcu::PixelBufferAccess &result)
{
    tcu::Surface errorMask(result.getWidth(), result.getHeight());
    bool anyError = false;

    tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toIVec());

    for (int y = 0; y < result.getHeight(); ++y)
        for (int x = 0; x < result.getWidth(); ++x)
        {
            const tcu::IVec4 pixel = result.getPixelInt(x, y);

            // allow green, yellow and any shade between
            if (pixel[1] != 255 || pixel[2] != 0)
            {
                errorMask.setPixel(x, y, tcu::RGBA::red());
                anyError = true;
            }
        }

    if (anyError)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("ResultImage", "Result image", result)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask) << tcu::TestLog::EndImageSet;
    }

    return !anyError;
}

static const char *const s_yellowishPosOnlyVertexSource =
    "${GLSL_VERSION_DECL}\n"
    "in highp vec4 a_position;\n"
    "out highp vec4 v_vertex_color;\n"
    "void main()\n"
    "{\n"
    "    gl_Position = a_position;\n"
    "    // yellowish shade\n"
    "    highp float redComponent = 0.5 + float(gl_VertexID % 5) / 8.0;\n"
    "    v_vertex_color = vec4(redComponent, 1.0, 0.0, 1.0);\n"
    "}\n";

static const char *const s_basicColorFragmentSource = "${GLSL_VERSION_DECL}\n"
                                                      "in mediump vec4 v_color;\n"
                                                      "layout(location = 0) out mediump vec4 o_color;\n"
                                                      "void main()\n"
                                                      "{\n"
                                                      "    o_color = v_color;\n"
                                                      "}\n";

static const char *const s_basicColorTessEvalSource = "${GLSL_VERSION_DECL}\n"
                                                      "${TESSELLATION_SHADER_REQUIRE}\n"
                                                      "${GPU_SHADER5_REQUIRE}\n"
                                                      "layout(triangles) in;\n"
                                                      "in highp vec4 v_tess_eval_color[];\n"
                                                      "out highp vec4 v_color;\n"
                                                      "precise gl_Position;\n"
                                                      "void main()\n"
                                                      "{\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"
                                                      "    v_color = gl_TessCoord.x * v_tess_eval_color[0]\n"
                                                      "            + gl_TessCoord.y * v_tess_eval_color[1]\n"
                                                      "            + gl_TessCoord.z * v_tess_eval_color[2];\n"
                                                      "}\n";

std::string ClearCase::genVertexSource(void) const
{
    return s_yellowishPosOnlyVertexSource;
}

std::string ClearCase::genFragmentSource(void) const
{
    return s_basicColorFragmentSource;
}

std::string ClearCase::genTessellationControlSource(bool setBBox) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${ARB_ES32_COMPATIBILITY_REQUIRE}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n";

    if (setBBox)
        buf << "${PRIMITIVE_BOUNDING_BOX_REQUIRE}\n";

    buf << "layout(vertices=3) out;\n"
           "in highp vec4 v_vertex_color[];\n"
           "out highp vec4 v_tess_eval_color[];\n"
           "void main()\n"
           "{\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
           "    v_tess_eval_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
           "    gl_TessLevelOuter[0] = 2.8;\n"
           "    gl_TessLevelOuter[1] = 2.8;\n"
           "    gl_TessLevelOuter[2] = 2.8;\n"
           "    gl_TessLevelInner[0] = 2.8;\n";

    if (setBBox)
    {
        buf << "\n"
               "    ${PRIM_GL_BOUNDING_BOX}[0] = min(min(gl_in[0].gl_Position,\n"
               "                                   gl_in[1].gl_Position),\n"
               "                               gl_in[2].gl_Position);\n"
               "    ${PRIM_GL_BOUNDING_BOX}[1] = max(max(gl_in[0].gl_Position,\n"
               "                                   gl_in[1].gl_Position),\n"
               "                               gl_in[2].gl_Position);\n";
    }

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

std::string ClearCase::genTessellationEvaluationSource(void) const
{
    return s_basicColorTessEvalSource;
}

class ViewportCallOrderCase : public TestCase
{
public:
    enum CallOrder
    {
        VIEWPORT_FIRST = 0,
        BBOX_FIRST,

        ORDER_LAST
    };

    ViewportCallOrderCase(Context &context, const char *name, const char *description, CallOrder callOrder);
    ~ViewportCallOrderCase(void);

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

    void genVbo(void);
    void genProgram(void);
    bool verifyImage(const tcu::PixelBufferAccess &result);

    std::string genVertexSource(void) const;
    std::string genFragmentSource(void) const;
    std::string genTessellationControlSource(void) const;
    std::string genTessellationEvaluationSource(void) const;

    const CallOrder m_callOrder;

    de::MovePtr<glu::Buffer> m_vbo;
    glw::GLuint m_vao;
    de::MovePtr<glu::ShaderProgram> m_program;
    int m_numVertices;
};

ViewportCallOrderCase::ViewportCallOrderCase(Context &context, const char *name, const char *description,
                                             CallOrder callOrder)
    : TestCase(context, name, description)
    , m_callOrder(callOrder)
    , m_vao(0)
    , m_numVertices(-1)
{
    DE_ASSERT(m_callOrder < ORDER_LAST);
}

ViewportCallOrderCase::~ViewportCallOrderCase(void)
{
    deinit();
}

void ViewportCallOrderCase::init(void)
{
    const bool hasES32OrGL45 = supportsES32OrGL45(m_context);

    if (!boundingBoxSupported(m_context))
        throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");

    if (!hasES32OrGL45 && !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Testing call order of state setting functions have no effect on the rendering.\n"
                       << "Setting viewport and bounding box in the following order:\n"
                       << ((m_callOrder == VIEWPORT_FIRST) ?
                               ("\tFirst viewport with glViewport function.\n") :
                               ("\tFirst bounding box with glPrimitiveBoundingBoxEXT function.\n"))
                       << ((m_callOrder == VIEWPORT_FIRST) ?
                               ("\tThen bounding box with glPrimitiveBoundingBoxEXT function.\n") :
                               ("\tThen viewport with glViewport function.\n"))
                       << "Verifying rendering result." << tcu::TestLog::EndMessage;

    // resources
    genVbo();
    genProgram();
}

void ViewportCallOrderCase::deinit(void)
{
    m_vbo.clear();
    m_program.clear();

    if (m_vao)
    {
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
        m_vao = 0;
    }
}

ViewportCallOrderCase::IterateResult ViewportCallOrderCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const tcu::IVec2 viewportSize =
        tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
    const glw::GLint posLocation = gl.getAttribLocation(m_program->getProgram(), "a_position");
    tcu::Surface resultSurface(viewportSize.x(), viewportSize.y());

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

    // set state
    for (int orderNdx = 0; orderNdx < 2; ++orderNdx)
    {
        if ((orderNdx == 0 && m_callOrder == VIEWPORT_FIRST) || (orderNdx == 1 && m_callOrder == BBOX_FIRST))
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Setting viewport to cover the left half of the render target.\n"
                               << "\t(0, 0, " << (viewportSize.x() / 2) << ", " << viewportSize.y() << ")"
                               << tcu::TestLog::EndMessage;

            gl.viewport(0, 0, viewportSize.x() / 2, viewportSize.y());
        }
        else
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Setting bounding box to cover the right half of the clip space.\n"
                               << "\t(0.0, -1.0, -1.0, 1.0) .. (1.0, 1.0, 1.0f, 1.0)" << tcu::TestLog::EndMessage;

            auto boundingBoxFunc = getBoundingBoxFunction(m_context);

            boundingBoxFunc(0.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
        }
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering mesh covering the right half of the clip space."
                       << tcu::TestLog::EndMessage;

    gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, sizeof(float[4]), (const float *)DE_NULL);
    gl.enableVertexAttribArray(posLocation);
    gl.useProgram(m_program->getProgram());
    gl.patchParameteri(GL_PATCH_VERTICES, 3);
    gl.drawArrays(GL_PATCHES, 0, m_numVertices);
    GLU_EXPECT_NO_ERROR(gl.getError(), "post-draw");

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying image" << tcu::TestLog::EndMessage;
    glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());

    if (!verifyImage(resultSurface.getAccess()))
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Result ok." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("Result", "Result", resultSurface.getAccess())
                           << tcu::TestLog::EndImageSet;

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

void ViewportCallOrderCase::genVbo(void)
{
    const int gridSize       = 6;
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    std::vector<tcu::Vec4> data(gridSize * gridSize * 2 * 3);
    std::vector<int> cellOrder(gridSize * gridSize * 2);
    de::Random rnd(0x55443322);

    // generate grid with triangles in random order
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
        cellOrder[ndx] = ndx;
    rnd.shuffle(cellOrder.begin(), cellOrder.end());

    // generate grid filling the right half of the clip space: (x: 0.0, y: -1.0) .. (x: 1.0, y: 1.0)
    for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
    {
        const int cellNdx   = cellOrder[ndx];
        const bool cellSide = ((cellNdx % 2) == 0);
        const int cellX     = (cellNdx / 2) % gridSize;
        const int cellY     = (cellNdx / 2) / gridSize;

        if (cellSide)
        {
            data[ndx * 3 + 0] = tcu::Vec4(float(cellX + 0) / float(gridSize),
                                          (float(cellY + 0) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
            data[ndx * 3 + 1] = tcu::Vec4(float(cellX + 1) / float(gridSize),
                                          (float(cellY + 1) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
            data[ndx * 3 + 2] = tcu::Vec4(float(cellX + 0) / float(gridSize),
                                          (float(cellY + 1) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
        }
        else
        {
            data[ndx * 3 + 0] = tcu::Vec4(float(cellX + 0) / float(gridSize),
                                          (float(cellY + 0) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
            data[ndx * 3 + 1] = tcu::Vec4(float(cellX + 1) / float(gridSize),
                                          (float(cellY + 0) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
            data[ndx * 3 + 2] = tcu::Vec4(float(cellX + 1) / float(gridSize),
                                          (float(cellY + 1) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
        }
    }

    // Generate VAO for desktop OpenGL
    if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
    {
        gl.genVertexArrays(1, &m_vao);
        gl.bindVertexArray(m_vao);
    }

    m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));
    gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
    gl.bufferData(GL_ARRAY_BUFFER, (int)(data.size() * sizeof(tcu::Vec4)), &data[0], GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "create vbo");

    m_numVertices = (int)data.size();
}

void ViewportCallOrderCase::genProgram(void)
{
    m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, genVertexSource().c_str()))
                              << glu::FragmentSource(specializeShader(m_context, genFragmentSource().c_str()))
                              << glu::TessellationControlSource(
                                     specializeShader(m_context, genTessellationControlSource().c_str()))
                              << glu::TessellationEvaluationSource(
                                     specializeShader(m_context, genTessellationEvaluationSource().c_str()))));

    m_testCtx.getLog() << tcu::TestLog::Section("Program", "Shader program") << *m_program << tcu::TestLog::EndSection;

    if (!m_program->isOk())
        throw tcu::TestError("shader build failed");
}

bool ViewportCallOrderCase::verifyImage(const tcu::PixelBufferAccess &result)
{
    const tcu::IVec2 insideBorder(deCeilFloatToInt32(0.25f * (float)result.getWidth()) + 1,
                                  deFloorFloatToInt32(0.5f * (float)result.getWidth()) - 1);
    const tcu::IVec2 outsideBorder(deFloorFloatToInt32(0.25f * (float)result.getWidth()) - 1,
                                   deCeilFloatToInt32(0.5f * (float)result.getWidth()) + 1);
    tcu::Surface errorMask(result.getWidth(), result.getHeight());
    bool anyError = false;

    tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toIVec());

    for (int y = 0; y < result.getHeight(); ++y)
        for (int x = 0; x < result.getWidth(); ++x)
        {
            /*
                Test stores the xmin,xmax values in the insideBorder and outsideBorder vectors.
                Thus the "y" in the vector is xmax.
            */

            const tcu::IVec4 pixel     = result.getPixelInt(x, y);
            const bool insideMeshArea  = x >= insideBorder.x() && x <= insideBorder.y();
            const bool outsideMeshArea = x <= outsideBorder.x() && x >= outsideBorder.y();

            // inside mesh, allow green, yellow and any shade between
            // outside mesh, allow background (black) only
            // in the border area, allow anything
            if ((insideMeshArea && (pixel[1] != 255 || pixel[2] != 0)) ||
                (outsideMeshArea && (pixel[0] != 0 || pixel[1] != 0 || pixel[2] != 0)))
            {
                errorMask.setPixel(x, y, tcu::RGBA::red());
                anyError = true;
            }
        }

    if (anyError)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("Images", "Image verification")
                           << tcu::TestLog::Image("ResultImage", "Result image", result)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask) << tcu::TestLog::EndImageSet;
    }

    return !anyError;
}

std::string ViewportCallOrderCase::genVertexSource(void) const
{
    return s_yellowishPosOnlyVertexSource;
}

std::string ViewportCallOrderCase::genFragmentSource(void) const
{
    return s_basicColorFragmentSource;
}

std::string ViewportCallOrderCase::genTessellationControlSource(void) const
{
    return "${GLSL_VERSION_DECL}\n"
           "${ARB_ES32_COMPATIBILITY_REQUIRE}\n"
           "${TESSELLATION_SHADER_REQUIRE}\n"
           "layout(vertices=3) out;\n"
           "in highp vec4 v_vertex_color[];\n"
           "out highp vec4 v_tess_eval_color[];\n"
           "void main()\n"
           "{\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
           "    v_tess_eval_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
           "    gl_TessLevelOuter[0] = 2.8;\n"
           "    gl_TessLevelOuter[1] = 2.8;\n"
           "    gl_TessLevelOuter[2] = 2.8;\n"
           "    gl_TessLevelInner[0] = 2.8;\n"
           "}\n";
}

std::string ViewportCallOrderCase::genTessellationEvaluationSource(void) const
{
    return s_basicColorTessEvalSource;
}

} // namespace

PrimitiveBoundingBoxTests::PrimitiveBoundingBoxTests(Context &context)
    : TestCaseGroup(context, "primitive_bounding_box", "Tests for EXT_primitive_bounding_box")
{
}

PrimitiveBoundingBoxTests::~PrimitiveBoundingBoxTests(void)
{
}

void PrimitiveBoundingBoxTests::init(void)
{
    static const struct
    {
        const char *name;
        const char *description;
        uint32_t methodFlags;
    } stateSetMethods[] = {
        {
            "global_state",
            "Set bounding box using PRIMITIVE_BOUNDING_BOX_EXT state",
            BBoxRenderCase::FLAG_SET_BBOX_STATE,
        },
        {
            "tessellation_set_per_draw",
            "Set bounding box using gl_BoundingBoxEXT, use same value for all primitives",
            BBoxRenderCase::FLAG_SET_BBOX_OUTPUT,
        },
        {
            "tessellation_set_per_primitive",
            "Set bounding box using gl_BoundingBoxEXT, use per-primitive bounding box",
            BBoxRenderCase::FLAG_SET_BBOX_OUTPUT | BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
        },
    };
    static const struct
    {
        const char *name;
        const char *description;
        uint32_t stageFlags;
    } pipelineConfigs[] = {
        {"vertex_fragment", "Render with vertex-fragment program", 0u},
        {"vertex_tessellation_fragment", "Render with vertex-tessellation{ctrl,eval}-fragment program",
         BBoxRenderCase::FLAG_TESSELLATION},
        {"vertex_geometry_fragment", "Render with vertex-tessellation{ctrl,eval}-geometry-fragment program",
         BBoxRenderCase::FLAG_GEOMETRY},
        {"vertex_tessellation_geometry_fragment", "Render with vertex-geometry-fragment program",
         BBoxRenderCase::FLAG_TESSELLATION | BBoxRenderCase::FLAG_GEOMETRY},
    };
    static const struct
    {
        const char *name;
        const char *description;
        uint32_t flags;
        uint32_t invalidFlags;
        uint32_t requiredFlags;
    } usageConfigs[] = {
        {"default_framebuffer_bbox_equal", "Render to default framebuffer, set tight bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX, 0},
        {"default_framebuffer_bbox_larger", "Render to default framebuffer, set padded bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_LARGER,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX, 0},
        {"default_framebuffer_bbox_smaller", "Render to default framebuffer, set too small bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_SMALLER,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX, 0},
        {"fbo_bbox_equal", "Render to texture, set tight bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX, 0},
        {"fbo_bbox_larger", "Render to texture, set padded bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_LARGER,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX, 0},
        {"fbo_bbox_smaller", "Render to texture, set too small bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_SMALLER,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX, 0},
        {"default_framebuffer", "Render to default framebuffer, set tight bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL, 0,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX},
        {"fbo", "Render to texture, set tight bounding box",
         BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL, 0,
         BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX},
    };
    enum PrimitiveRenderType
    {
        TYPE_TRIANGLE,
        TYPE_LINE,
        TYPE_POINT,
    };
    const struct
    {
        const char *name;
        const char *description;
        PrimitiveRenderType type;
        uint32_t flags;
    } primitiveTypes[] = {
        {"triangles", "Triangle render tests", TYPE_TRIANGLE, 0},
        {"lines", "Line render tests", TYPE_LINE, 0},
        {"points", "Point render tests", TYPE_POINT, 0},
        {"wide_lines", "Wide line render tests", TYPE_LINE, LineRenderCase::LINEFLAG_WIDE},
        {"wide_points", "Wide point render tests", TYPE_POINT, PointRenderCase::POINTFLAG_WIDE},
    };

    // .state_query
    {
        tcu::TestCaseGroup *const stateQueryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "State queries");
        addChild(stateQueryGroup);

        stateQueryGroup->addChild(new InitialValueCase(m_context, "initial_value", "Initial value case"));
        stateQueryGroup->addChild(new QueryCase(m_context, "getfloat", "getFloatv", QueryCase::QUERY_FLOAT));
        stateQueryGroup->addChild(new QueryCase(m_context, "getboolean", "getBooleanv", QueryCase::QUERY_BOOLEAN));
        stateQueryGroup->addChild(new QueryCase(m_context, "getinteger", "getIntegerv", QueryCase::QUERY_INT));
        stateQueryGroup->addChild(new QueryCase(m_context, "getinteger64", "getInteger64v", QueryCase::QUERY_INT64));
    }

    // .triangles
    // .(wide_)lines
    // .(wide_)points
    for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); ++primitiveTypeNdx)
    {
        tcu::TestCaseGroup *const primitiveGroup = new tcu::TestCaseGroup(
            m_testCtx, primitiveTypes[primitiveTypeNdx].name, primitiveTypes[primitiveTypeNdx].description);
        addChild(primitiveGroup);

        for (int stateSetMethodNdx = 0; stateSetMethodNdx < DE_LENGTH_OF_ARRAY(stateSetMethods); ++stateSetMethodNdx)
        {
            tcu::TestCaseGroup *const methodGroup = new tcu::TestCaseGroup(
                m_testCtx, stateSetMethods[stateSetMethodNdx].name, stateSetMethods[stateSetMethodNdx].description);
            primitiveGroup->addChild(methodGroup);

            for (int pipelineConfigNdx = 0; pipelineConfigNdx < DE_LENGTH_OF_ARRAY(pipelineConfigs);
                 ++pipelineConfigNdx)
            {
                if ((stateSetMethods[stateSetMethodNdx].methodFlags & BBoxRenderCase::FLAG_SET_BBOX_OUTPUT) != 0 &&
                    (pipelineConfigs[pipelineConfigNdx].stageFlags & BBoxRenderCase::FLAG_TESSELLATION) == 0)
                {
                    // invalid config combination
                }
                else
                {
                    tcu::TestCaseGroup *const pipelineGroup =
                        new tcu::TestCaseGroup(m_testCtx, pipelineConfigs[pipelineConfigNdx].name,
                                               pipelineConfigs[pipelineConfigNdx].description);
                    methodGroup->addChild(pipelineGroup);

                    for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageConfigs); ++usageNdx)
                    {
                        const uint32_t flags =
                            primitiveTypes[primitiveTypeNdx].flags | stateSetMethods[stateSetMethodNdx].methodFlags |
                            pipelineConfigs[pipelineConfigNdx].stageFlags | usageConfigs[usageNdx].flags;

                        if (usageConfigs[usageNdx].invalidFlags && (flags & usageConfigs[usageNdx].invalidFlags) != 0)
                            continue;
                        if (usageConfigs[usageNdx].requiredFlags && (flags & usageConfigs[usageNdx].requiredFlags) == 0)
                            continue;

                        switch (primitiveTypes[primitiveTypeNdx].type)
                        {
                        case TYPE_TRIANGLE:
                            pipelineGroup->addChild(new GridRenderCase(m_context, usageConfigs[usageNdx].name,
                                                                       usageConfigs[usageNdx].description, flags));
                            break;
                        case TYPE_LINE:
                            pipelineGroup->addChild(new LineRenderCase(m_context, usageConfigs[usageNdx].name,
                                                                       usageConfigs[usageNdx].description, flags));
                            break;
                        case TYPE_POINT:
                            pipelineGroup->addChild(new PointRenderCase(m_context, usageConfigs[usageNdx].name,
                                                                        usageConfigs[usageNdx].description, flags));
                            break;
                        default:
                            DE_ASSERT(false);
                        }
                    }
                }
            }
        }
    }

    // .depth
    {
        static const struct
        {
            const char *name;
            const char *description;
            DepthDrawCase::DepthType depthMethod;
        } depthMethods[] = {
            {"builtin_depth", "Fragment depth not modified in fragment shader", DepthDrawCase::DEPTH_BUILTIN},
            {"user_defined_depth", "Fragment depth is defined in the fragment shader",
             DepthDrawCase::DEPTH_USER_DEFINED},
        };
        static const struct
        {
            const char *name;
            const char *description;
            DepthDrawCase::BBoxState bboxState;
            DepthDrawCase::BBoxSize bboxSize;
        } depthCases[] = {
            {
                "global_state_bbox_equal",
                "Test tight bounding box with global bbox state",
                DepthDrawCase::STATE_GLOBAL,
                DepthDrawCase::BBOX_EQUAL,
            },
            {
                "global_state_bbox_larger",
                "Test padded bounding box with global bbox state",
                DepthDrawCase::STATE_GLOBAL,
                DepthDrawCase::BBOX_LARGER,
            },
            {
                "per_primitive_bbox_equal",
                "Test tight bounding box with tessellation output bbox",
                DepthDrawCase::STATE_PER_PRIMITIVE,
                DepthDrawCase::BBOX_EQUAL,
            },
            {
                "per_primitive_bbox_larger",
                "Test padded bounding box with tessellation output bbox",
                DepthDrawCase::STATE_PER_PRIMITIVE,
                DepthDrawCase::BBOX_LARGER,
            },
        };

        tcu::TestCaseGroup *const depthGroup =
            new tcu::TestCaseGroup(m_testCtx, "depth", "Test bounding box depth component");
        addChild(depthGroup);

        // .builtin_depth
        // .user_defined_depth
        for (int depthNdx = 0; depthNdx < DE_LENGTH_OF_ARRAY(depthMethods); ++depthNdx)
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, depthMethods[depthNdx].name, depthMethods[depthNdx].description);
            depthGroup->addChild(group);

            for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(depthCases); ++caseNdx)
                group->addChild(new DepthDrawCase(m_context, depthCases[caseNdx].name, depthCases[caseNdx].description,
                                                  depthMethods[depthNdx].depthMethod, depthCases[caseNdx].bboxState,
                                                  depthCases[caseNdx].bboxSize));
        }
    }

    // .blit_fbo
    {
        tcu::TestCaseGroup *const blitFboGroup =
            new tcu::TestCaseGroup(m_testCtx, "blit_fbo", "Test bounding box does not affect blitting");
        addChild(blitFboGroup);

        blitFboGroup->addChild(new BlitFboCase(m_context, "blit_default_to_fbo", "Blit from default fb to fbo",
                                               BlitFboCase::TARGET_DEFAULT, BlitFboCase::TARGET_FBO));
        blitFboGroup->addChild(new BlitFboCase(m_context, "blit_fbo_to_default", "Blit from fbo to default fb",
                                               BlitFboCase::TARGET_FBO, BlitFboCase::TARGET_DEFAULT));
        blitFboGroup->addChild(new BlitFboCase(m_context, "blit_fbo_to_fbo", "Blit from fbo to fbo",
                                               BlitFboCase::TARGET_FBO, BlitFboCase::TARGET_FBO));
    }

    // .clear
    {
        tcu::TestCaseGroup *const clearGroup =
            new tcu::TestCaseGroup(m_testCtx, "clear", "Test bounding box does not clears");
        addChild(clearGroup);

        clearGroup->addChild(new ClearCase(m_context, "full_clear", "Do full clears", 0));
        clearGroup->addChild(new ClearCase(m_context, "full_clear_with_triangles",
                                           "Do full clears and render some geometry", ClearCase::DRAW_TRIANGLE_BIT));
        clearGroup->addChild(new ClearCase(m_context, "full_clear_with_triangles_per_primitive_bbox",
                                           "Do full clears and render some geometry",
                                           ClearCase::DRAW_TRIANGLE_BIT | ClearCase::PER_PRIMITIVE_BBOX_BIT));
        clearGroup->addChild(
            new ClearCase(m_context, "scissored_clear", "Do scissored clears", ClearCase::SCISSOR_CLEAR_BIT));
        clearGroup->addChild(new ClearCase(m_context, "scissored_clear_with_triangles",
                                           "Do scissored clears and render some geometry",
                                           ClearCase::SCISSOR_CLEAR_BIT | ClearCase::DRAW_TRIANGLE_BIT));
        clearGroup->addChild(new ClearCase(m_context, "scissored_clear_with_triangles_per_primitive_bbox",
                                           "Do scissored clears and render some geometry",
                                           ClearCase::SCISSOR_CLEAR_BIT | ClearCase::DRAW_TRIANGLE_BIT |
                                               ClearCase::PER_PRIMITIVE_BBOX_BIT));
        clearGroup->addChild(new ClearCase(m_context, "scissored_full_clear", "Do full clears with enabled scissor",
                                           ClearCase::FULLSCREEN_SCISSOR_BIT | ClearCase::SCISSOR_CLEAR_BIT));
        clearGroup->addChild(new ClearCase(m_context, "scissored_full_clear_with_triangles",
                                           "Do full clears with enabled scissor and render some geometry",
                                           ClearCase::FULLSCREEN_SCISSOR_BIT | ClearCase::SCISSOR_CLEAR_BIT |
                                               ClearCase::DRAW_TRIANGLE_BIT));
        clearGroup->addChild(new ClearCase(m_context, "scissored_full_clear_with_triangles_per_primitive_bbox",
                                           "Do full clears with enabled scissor and render some geometry",
                                           ClearCase::FULLSCREEN_SCISSOR_BIT | ClearCase::SCISSOR_CLEAR_BIT |
                                               ClearCase::DRAW_TRIANGLE_BIT | ClearCase::PER_PRIMITIVE_BBOX_BIT));
    }

    // .call_order (Khronos bug #13262)
    {
        tcu::TestCaseGroup *const callOrderGroup =
            new tcu::TestCaseGroup(m_testCtx, "call_order", "Test viewport and bounding box calls have no effect");
        addChild(callOrderGroup);

        callOrderGroup->addChild(new ViewportCallOrderCase(m_context, "viewport_first_bbox_second",
                                                           "Set up viewport first and bbox after",
                                                           ViewportCallOrderCase::VIEWPORT_FIRST));
        callOrderGroup->addChild(new ViewportCallOrderCase(m_context, "bbox_first_viewport_second",
                                                           "Set up bbox first and viewport after",
                                                           ViewportCallOrderCase::BBOX_FIRST));
    }
}

} // namespace Functional
} // namespace gles31
} // namespace deqp
