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

#include "es31fTessellationGeometryInteractionTests.hpp"

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

#include <sstream>
#include <algorithm>
#include <iterator>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static std::string specializeShader(const std::string &shaderSource, const glu::ContextType &contextType)
{
    const bool supportsES32orGL45 = glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
                                    glu::contextSupports(contextType, glu::ApiType::core(4, 5));

    const bool supportsGL45 = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

    std::map<std::string, std::string> shaderArgs;

    shaderArgs["VERSION_DECL"] = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(contextType));
    shaderArgs["EXTENSION_GEOMETRY_SHADER"] =
        (supportsES32orGL45) ? ("") : ("#extension GL_EXT_geometry_shader : require\n");
    shaderArgs["EXTENSION_TESSELATION_SHADER"] =
        (supportsES32orGL45) ? ("") : ("#extension GL_EXT_tessellation_shader : require\n");
    shaderArgs["EXTENSION_TESSELATION_POINT_SIZE"] =
        (supportsGL45) ? ("") : ("#extension GL_EXT_tessellation_point_size : require\n");
    shaderArgs["EXTENSION_GEOMETRY_POINT_SIZE"] =
        (supportsGL45) ? ("") : ("#extension GL_EXT_geometry_point_size : require\n");

    return tcu::StringTemplate(shaderSource).specialize(shaderArgs);
}

static const char *const s_positionVertexShader      = "${VERSION_DECL}\n"
                                                       "in highp vec4 a_position;\n"
                                                       "void main (void)\n"
                                                       "{\n"
                                                       "    gl_Position = a_position;\n"
                                                       "}\n";
static const char *const s_whiteOutputFragmentShader = "${VERSION_DECL}\n"
                                                       "layout(location = 0) out mediump vec4 fragColor;\n"
                                                       "void main (void)\n"
                                                       "{\n"
                                                       "    fragColor = vec4(1.0);\n"
                                                       "}\n";

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

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

protected:
    std::string getVertexSource(void) const;
    std::string getFragmentSource(void) const;
};

IdentityShaderCase::IdentityShaderCase(Context &context, const char *name, const char *description)
    : TestCase(context, name, description)
{
}

std::string IdentityShaderCase::getVertexSource(void) const
{
    std::string source = "${VERSION_DECL}\n"
                         "in highp vec4 a_position;\n"
                         "out highp vec4 v_vertex_color;\n"
                         "void main (void)\n"
                         "{\n"
                         "    gl_Position = a_position;\n"
                         "    v_vertex_color = vec4(a_position.x * 0.5 + 0.5, a_position.y * 0.5 + 0.5, 1.0, 0.4);\n"
                         "}\n";

    return specializeShader(source, m_context.getRenderContext().getType());
}

std::string IdentityShaderCase::getFragmentSource(void) const
{
    std::string source = "${VERSION_DECL}\n"
                         "in mediump vec4 v_fragment_color;\n"
                         "layout(location = 0) out mediump vec4 fragColor;\n"
                         "void main (void)\n"
                         "{\n"
                         "    fragColor = v_fragment_color;\n"
                         "}\n";

    return specializeShader(source, m_context.getRenderContext().getType());
}

class IdentityGeometryShaderCase : public IdentityShaderCase
{
public:
    enum CaseType
    {
        CASE_TRIANGLES = 0,
        CASE_QUADS,
        CASE_ISOLINES,
    };

    IdentityGeometryShaderCase(Context &context, const char *name, const char *description, CaseType caseType);
    ~IdentityGeometryShaderCase(void);

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

    std::string getTessellationControlSource(void) const;
    std::string getTessellationEvaluationSource(bool geometryActive) const;
    std::string getGeometrySource(void) const;

    enum
    {
        RENDER_SIZE = 128,
    };

    const CaseType m_case;
    uint32_t m_patchBuffer;
};

IdentityGeometryShaderCase::IdentityGeometryShaderCase(Context &context, const char *name, const char *description,
                                                       CaseType caseType)
    : IdentityShaderCase(context, name, description)
    , m_case(caseType)
    , m_patchBuffer(0)
{
}

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

void IdentityGeometryShaderCase::init(void)
{
    // Requirements
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
                                !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");

    if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
        throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
                                     de::toString<int>(RENDER_SIZE) + " or larger render target.");

    // Log

    m_testCtx.getLog()
        << tcu::TestLog::Message
        << "Testing tessellating shader program output does not change when a passthrough geometry shader is "
           "attached.\n"
        << "Rendering two images, first with and second without a geometry shader. Expecting similar results.\n"
        << "Using additive blending to detect overlap.\n"
        << tcu::TestLog::EndMessage;

    // Resources

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

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

        gl.genBuffers(1, &m_patchBuffer);
        gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
        gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
    }
}

void IdentityGeometryShaderCase::deinit(void)
{
    if (m_patchBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer);
        m_patchBuffer = 0;
    }
}

IdentityGeometryShaderCase::IterateResult IdentityGeometryShaderCase::iterate(void)
{
    const float innerTessellationLevel = 14.0f;
    const float outerTessellationLevel = 14.0f;
    const glw::Functions &gl           = m_context.getRenderContext().getFunctions();
    tcu::Surface resultWithGeometry(RENDER_SIZE, RENDER_SIZE);
    tcu::Surface resultWithoutGeometry(RENDER_SIZE, RENDER_SIZE);

    const struct
    {
        const char *name;
        const char *description;
        bool containsGeometryShader;
        tcu::PixelBufferAccess surfaceAccess;
    } renderTargets[] = {
        {"RenderWithGeometryShader", "Render with geometry shader", true, resultWithGeometry.getAccess()},
        {"RenderWithoutGeometryShader", "Render without geometry shader", false, resultWithoutGeometry.getAccess()},
    };

    gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport");

    gl.enable(GL_BLEND);
    gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
    gl.blendEquation(GL_FUNC_ADD);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");

    m_testCtx.getLog() << tcu::TestLog::Message << "Tessellation level: inner " << innerTessellationLevel << ", outer "
                       << outerTessellationLevel << tcu::TestLog::EndMessage;

    // render with and without geometry shader
    for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), renderTargets[renderNdx].name,
                                            renderTargets[renderNdx].description);
        glu::ProgramSources sources;

        sources << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
                << glu::TessellationControlSource(getTessellationControlSource())
                << glu::TessellationEvaluationSource(
                       getTessellationEvaluationSource(renderTargets[renderNdx].containsGeometryShader));

        if (renderTargets[renderNdx].containsGeometryShader)
            sources << glu::GeometrySource(getGeometrySource());

        {
            const glu::ShaderProgram program(m_context.getRenderContext(), sources);
            const glu::VertexArray vao(m_context.getRenderContext());
            const int posLocation          = gl.getAttribLocation(program.getProgram(), "a_position");
            const int innerTessellationLoc = gl.getUniformLocation(program.getProgram(), "u_innerTessellationLevel");
            const int outerTessellationLoc = gl.getUniformLocation(program.getProgram(), "u_outerTessellationLevel");

            m_testCtx.getLog() << program;

            if (!program.isOk())
                throw tcu::TestError("could not build program");
            if (posLocation == -1)
                throw tcu::TestError("a_position location was -1");
            if (outerTessellationLoc == -1)
                throw tcu::TestError("u_outerTessellationLevel location was -1");

            gl.bindVertexArray(*vao);
            gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
            gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            gl.enableVertexAttribArray(posLocation);
            GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");

            gl.useProgram(program.getProgram());
            gl.uniform1f(outerTessellationLoc, outerTessellationLevel);

            if (innerTessellationLoc == -1)
                gl.uniform1f(innerTessellationLoc, innerTessellationLevel);

            GLU_EXPECT_NO_ERROR(gl.getError(), "use program");

            gl.patchParameteri(GL_PATCH_VERTICES, (m_case == CASE_TRIANGLES) ? (3) : (4));
            GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");

            gl.clear(GL_COLOR_BUFFER_BIT);
            GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

            gl.drawArrays(GL_PATCHES, 0, 4);
            GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");

            glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess);
        }
    }

    if (tcu::intThresholdPositionDeviationCompare(m_testCtx.getLog(), "ImageCompare", "Image comparison",
                                                  resultWithoutGeometry.getAccess(), resultWithGeometry.getAccess(),
                                                  tcu::UVec4(8, 8, 8, 255), tcu::IVec3(1, 1, 0), true,
                                                  tcu::COMPARE_LOG_RESULT))
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");

    return STOP;
}

std::string IdentityGeometryShaderCase::getTessellationControlSource(void) const
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout(vertices = 4) out;\n"
           "\n"
           "uniform highp float u_innerTessellationLevel;\n"
           "uniform highp float u_outerTessellationLevel;\n"
           "in highp vec4 v_vertex_color[];\n"
           "out highp vec4 v_patch_color[];\n"
           "\n"
           "void main (void)\n"
           "{\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
           "    v_patch_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
           "\n";

    if (m_case == CASE_TRIANGLES)
        buf << "    gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
               "    gl_TessLevelOuter[1] = u_outerTessellationLevel;\n"
               "    gl_TessLevelOuter[2] = u_outerTessellationLevel;\n"
               "    gl_TessLevelInner[0] = u_innerTessellationLevel;\n";
    else if (m_case == CASE_QUADS)
        buf << "    gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
               "    gl_TessLevelOuter[1] = u_outerTessellationLevel;\n"
               "    gl_TessLevelOuter[2] = u_outerTessellationLevel;\n"
               "    gl_TessLevelOuter[3] = u_outerTessellationLevel;\n"
               "    gl_TessLevelInner[0] = u_innerTessellationLevel;\n"
               "    gl_TessLevelInner[1] = u_innerTessellationLevel;\n";
    else if (m_case == CASE_ISOLINES)
        buf << "    gl_TessLevelOuter[0] = u_outerTessellationLevel;\n"
               "    gl_TessLevelOuter[1] = u_outerTessellationLevel;\n";
    else
        DE_ASSERT(false);

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string IdentityGeometryShaderCase::getTessellationEvaluationSource(bool geometryActive) const
{
    const char *const colorOutputName = ((geometryActive) ? ("v_evaluated_color") : ("v_fragment_color"));
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout("
        << ((m_case == CASE_TRIANGLES) ? ("triangles") :
            (m_case == CASE_QUADS)     ? ("quads") :
                                         ("isolines"))
        << ") in;\n"
           "\n"
           "in highp vec4 v_patch_color[];\n"
           "out highp vec4 "
        << colorOutputName
        << ";\n"
           "\n"
           "// note: No need to use precise gl_Position since we do not require gapless geometry\n"
           "void main (void)\n"
           "{\n";

    if (m_case == CASE_TRIANGLES)
        buf << "    vec3 weights = vec3(pow(gl_TessCoord.x, 1.3), pow(gl_TessCoord.y, 1.3), pow(gl_TessCoord.z, "
               "1.3));\n"
               "    vec3 cweights = gl_TessCoord;\n"
               "    gl_Position = vec4(weights.x * gl_in[0].gl_Position.xyz + weights.y * gl_in[1].gl_Position.xyz + "
               "weights.z * gl_in[2].gl_Position.xyz, 1.0);\n"
               "    "
            << colorOutputName
            << " = cweights.x * v_patch_color[0] + cweights.y * v_patch_color[1] + cweights.z * v_patch_color[2];\n";
    else if (m_case == CASE_QUADS || m_case == CASE_ISOLINES)
        buf << "    vec2 normalizedCoord = (gl_TessCoord.xy * 2.0 - vec2(1.0));\n"
               "    vec2 normalizedWeights = normalizedCoord * (vec2(1.0) - 0.3 * cos(normalizedCoord.yx * 1.57));\n"
               "    vec2 weights = normalizedWeights * 0.5 + vec2(0.5);\n"
               "    vec2 cweights = gl_TessCoord.xy;\n"
               "    gl_Position = mix(mix(gl_in[0].gl_Position, gl_in[1].gl_Position, weights.y), "
               "mix(gl_in[2].gl_Position, gl_in[3].gl_Position, weights.y), weights.x);\n"
               "    "
            << colorOutputName
            << " = mix(mix(v_patch_color[0], v_patch_color[1], cweights.y), mix(v_patch_color[2], v_patch_color[3], "
               "cweights.y), cweights.x);\n";
    else
        DE_ASSERT(false);

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string IdentityGeometryShaderCase::getGeometrySource(void) const
{
    const char *const geometryInputPrimitive  = (m_case == CASE_ISOLINES) ? ("lines") : ("triangles");
    const char *const geometryOutputPrimitive = (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip");
    const int numEmitVertices                 = (m_case == CASE_ISOLINES) ? (2) : (3);
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_GEOMETRY_SHADER}"
           "layout("
        << geometryInputPrimitive
        << ") in;\n"
           "layout("
        << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices
        << ") out;\n"
           "\n"
           "in highp vec4 v_evaluated_color[];\n"
           "out highp vec4 v_fragment_color;\n"
           "\n"
           "void main (void)\n"
           "{\n"
           "    for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n"
           "    {\n"
           "        gl_Position = gl_in[ndx].gl_Position;\n"
           "        v_fragment_color = v_evaluated_color[ndx];\n"
           "        EmitVertex();\n"
           "    }\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

class IdentityTessellationShaderCase : public IdentityShaderCase
{
public:
    enum CaseType
    {
        CASE_TRIANGLES = 0,
        CASE_ISOLINES,
    };

    IdentityTessellationShaderCase(Context &context, const char *name, const char *description, CaseType caseType);
    ~IdentityTessellationShaderCase(void);

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

    std::string getTessellationControlSource(void) const;
    std::string getTessellationEvaluationSource(void) const;
    std::string getGeometrySource(bool tessellationActive) const;

    enum
    {
        RENDER_SIZE = 256,
    };

    const CaseType m_case;
    uint32_t m_dataBuffer;
};

IdentityTessellationShaderCase::IdentityTessellationShaderCase(Context &context, const char *name,
                                                               const char *description, CaseType caseType)
    : IdentityShaderCase(context, name, description)
    , m_case(caseType)
    , m_dataBuffer(0)
{
}

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

void IdentityTessellationShaderCase::init(void)
{
    // Requirements
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
                                !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");

    if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
        throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
                                     de::toString<int>(RENDER_SIZE) + " or larger render target.");

    // Log

    m_testCtx.getLog()
        << tcu::TestLog::Message
        << "Testing geometry shading shader program output does not change when a passthrough tessellation shader is "
           "attached.\n"
        << "Rendering two images, first with and second without a tessellation shader. Expecting similar results.\n"
        << "Using additive blending to detect overlap.\n"
        << tcu::TestLog::EndMessage;

    // Resources

    {
        static const tcu::Vec4 pointData[] = {
            tcu::Vec4(-0.4f, 0.4f, 0.0f, 1.0f),
            tcu::Vec4(0.0f, -0.5f, 0.0f, 1.0f),
            tcu::Vec4(0.4f, 0.4f, 0.0f, 1.0f),
        };
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();

        gl.genBuffers(1, &m_dataBuffer);
        gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer);
        gl.bufferData(GL_ARRAY_BUFFER, sizeof(pointData), pointData, GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
    }
}

void IdentityTessellationShaderCase::deinit(void)
{
    if (m_dataBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dataBuffer);
        m_dataBuffer = 0;
    }
}

IdentityTessellationShaderCase::IterateResult IdentityTessellationShaderCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::Surface resultWithTessellation(RENDER_SIZE, RENDER_SIZE);
    tcu::Surface resultWithoutTessellation(RENDER_SIZE, RENDER_SIZE);
    const int numPrimitiveVertices = (m_case == CASE_TRIANGLES) ? (3) : (2);

    const struct
    {
        const char *name;
        const char *description;
        bool containsTessellationShaders;
        tcu::PixelBufferAccess surfaceAccess;
    } renderTargets[] = {
        {"RenderWithTessellationShader", "Render with tessellation shader", true, resultWithTessellation.getAccess()},
        {"RenderWithoutTessellationShader", "Render without tessellation shader", false,
         resultWithoutTessellation.getAccess()},
    };

    gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set viewport");

    gl.enable(GL_BLEND);
    gl.blendFunc(GL_SRC_ALPHA, GL_ONE);
    gl.blendEquation(GL_FUNC_ADD);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");

    // render with and without tessellation shader
    for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renderTargets); ++renderNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), renderTargets[renderNdx].name,
                                            renderTargets[renderNdx].description);
        glu::ProgramSources sources;

        sources << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
                << glu::GeometrySource(getGeometrySource(renderTargets[renderNdx].containsTessellationShaders));

        if (renderTargets[renderNdx].containsTessellationShaders)
            sources << glu::TessellationControlSource(getTessellationControlSource())
                    << glu::TessellationEvaluationSource(getTessellationEvaluationSource());

        {
            const glu::ShaderProgram program(m_context.getRenderContext(), sources);
            const glu::VertexArray vao(m_context.getRenderContext());
            const int posLocation = gl.getAttribLocation(program.getProgram(), "a_position");

            m_testCtx.getLog() << program;

            if (!program.isOk())
                throw tcu::TestError("could not build program");
            if (posLocation == -1)
                throw tcu::TestError("a_position location was -1");

            gl.bindVertexArray(*vao);
            gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBuffer);
            gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            gl.enableVertexAttribArray(posLocation);
            GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");

            gl.useProgram(program.getProgram());
            GLU_EXPECT_NO_ERROR(gl.getError(), "use program");

            gl.clear(GL_COLOR_BUFFER_BIT);
            GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

            if (renderTargets[renderNdx].containsTessellationShaders)
            {
                gl.patchParameteri(GL_PATCH_VERTICES, numPrimitiveVertices);
                GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");

                gl.drawArrays(GL_PATCHES, 0, numPrimitiveVertices);
                GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
            }
            else
            {
                gl.drawArrays((m_case == CASE_TRIANGLES) ? (GL_TRIANGLES) : (GL_LINES), 0, numPrimitiveVertices);
                GLU_EXPECT_NO_ERROR(gl.getError(), "draw primitives");
            }

            glu::readPixels(m_context.getRenderContext(), 0, 0, renderTargets[renderNdx].surfaceAccess);
        }
    }

    // compare
    {
        bool imageOk;

        if (m_context.getRenderTarget().getNumSamples() > 1)
            imageOk = tcu::fuzzyCompare(m_testCtx.getLog(), "ImageCompare", "Image comparison",
                                        resultWithoutTessellation.getAccess(), resultWithTessellation.getAccess(),
                                        0.03f, tcu::COMPARE_LOG_RESULT);
        else
            imageOk = tcu::intThresholdPositionDeviationCompare(
                m_testCtx.getLog(), "ImageCompare", "Image comparison", resultWithoutTessellation.getAccess(),
                resultWithTessellation.getAccess(), tcu::UVec4(8, 8, 8, 255), //!< threshold
                tcu::IVec3(1, 1, 0),                                          //!< 3x3 search kernel
                true, //!< fragments may end up over the viewport, just ignore them
                tcu::COMPARE_LOG_RESULT);

        if (imageOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        else
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
    }

    return STOP;
}

std::string IdentityTessellationShaderCase::getTessellationControlSource(void) const
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout(vertices = "
        << ((m_case == CASE_TRIANGLES) ? (3) : (2))
        << ") out;\n"
           "\n"
           "in highp vec4 v_vertex_color[];\n"
           "out highp vec4 v_control_color[];\n"
           "\n"
           "void main (void)\n"
           "{\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
           "    v_control_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
           "\n";

    if (m_case == CASE_TRIANGLES)
        buf << "    gl_TessLevelOuter[0] = 1.0;\n"
               "    gl_TessLevelOuter[1] = 1.0;\n"
               "    gl_TessLevelOuter[2] = 1.0;\n"
               "    gl_TessLevelInner[0] = 1.0;\n";
    else if (m_case == CASE_ISOLINES)
        buf << "    gl_TessLevelOuter[0] = 1.0;\n"
               "    gl_TessLevelOuter[1] = 1.0;\n";
    else
        DE_ASSERT(false);

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string IdentityTessellationShaderCase::getTessellationEvaluationSource(void) const
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout("
        << ((m_case == CASE_TRIANGLES) ? ("triangles") : ("isolines"))
        << ") in;\n"
           "\n"
           "in highp vec4 v_control_color[];\n"
           "out highp vec4 v_evaluated_color;\n"
           "\n"
           "// note: No need to use precise gl_Position since we do not require gapless geometry\n"
           "void main (void)\n"
           "{\n";

    if (m_case == CASE_TRIANGLES)
        buf << "    gl_Position = 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"
               "    v_evaluated_color = gl_TessCoord.x * v_control_color[0] + gl_TessCoord.y * v_control_color[1] + "
               "gl_TessCoord.z * v_control_color[2];\n";
    else if (m_case == CASE_ISOLINES)
        buf << "    gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);\n"
               "    v_evaluated_color = mix(v_control_color[0], v_control_color[1], gl_TessCoord.x);\n";
    else
        DE_ASSERT(false);

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string IdentityTessellationShaderCase::getGeometrySource(bool tessellationActive) const
{
    const char *const colorSourceName         = (tessellationActive) ? ("v_evaluated_color") : ("v_vertex_color");
    const char *const geometryInputPrimitive  = (m_case == CASE_ISOLINES) ? ("lines") : ("triangles");
    const char *const geometryOutputPrimitive = (m_case == CASE_ISOLINES) ? ("line_strip") : ("triangle_strip");
    const int numEmitVertices                 = (m_case == CASE_ISOLINES) ? (11) : (8);
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_GEOMETRY_SHADER}"
           "layout("
        << geometryInputPrimitive
        << ") in;\n"
           "layout("
        << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices
        << ") out;\n"
           "\n"
           "in highp vec4 "
        << colorSourceName
        << "[];\n"
           "out highp vec4 v_fragment_color;\n"
           "\n"
           "void main (void)\n"
           "{\n";

    if (m_case == CASE_TRIANGLES)
    {
        buf << "    vec4 centerPos = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position) / 3.0f;\n"
               "\n"
               "    for (int ndx = 0; ndx < 4; ++ndx)\n"
               "    {\n"
               "        gl_Position = centerPos + (centerPos - gl_in[ndx % 3].gl_Position);\n"
               "        v_fragment_color = "
            << colorSourceName
            << "[ndx % 3];\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = centerPos + 0.7 * (centerPos - gl_in[ndx % 3].gl_Position);\n"
               "        v_fragment_color = "
            << colorSourceName
            << "[ndx % 3];\n"
               "        EmitVertex();\n"
               "    }\n";
    }
    else if (m_case == CASE_ISOLINES)
    {
        buf << "    vec4 mdir = vec4(gl_in[0].gl_Position.y - gl_in[1].gl_Position.y, gl_in[1].gl_Position.x - "
               "gl_in[0].gl_Position.x, 0.0, 0.0);\n"
               "    for (int i = 0; i <= 10; ++i)\n"
               "    {\n"
               "        float xweight = cos(float(i) / 10.0 * 6.28) * 0.5 + 0.5;\n"
               "        float mweight = sin(float(i) / 10.0 * 6.28) * 0.1 + 0.1;\n"
               "        gl_Position = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, xweight) + mweight * mdir;\n"
               "        v_fragment_color = mix("
            << colorSourceName << "[0], " << colorSourceName
            << "[1], xweight);\n"
               "        EmitVertex();\n"
               "    }\n";
    }
    else
        DE_ASSERT(false);

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

class FeedbackPrimitiveTypeCase : public TestCase
{
public:
    enum TessellationOutputType
    {
        TESSELLATION_OUT_TRIANGLES = 0,
        TESSELLATION_OUT_QUADS,
        TESSELLATION_OUT_ISOLINES,

        TESSELLATION_OUT_LAST
    };
    enum TessellationPointMode
    {
        TESSELLATION_POINTMODE_OFF = 0,
        TESSELLATION_POINTMODE_ON,

        TESSELLATION_POINTMODE_LAST
    };
    enum GeometryOutputType
    {
        GEOMETRY_OUTPUT_POINTS = 0,
        GEOMETRY_OUTPUT_LINES,
        GEOMETRY_OUTPUT_TRIANGLES,

        GEOMETRY_OUTPUT_LAST
    };

    FeedbackPrimitiveTypeCase(Context &context, const char *name, const char *description,
                              TessellationOutputType tessellationOutput, TessellationPointMode tessellationPointMode,
                              GeometryOutputType geometryOutputType);
    ~FeedbackPrimitiveTypeCase(void);

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

    void renderWithFeedback(tcu::Surface &dst);
    void renderWithoutFeedback(tcu::Surface &dst);
    void verifyFeedbackResults(const std::vector<tcu::Vec4> &feedbackResult);
    void verifyRenderedImage(const tcu::Surface &image, const std::vector<tcu::Vec4> &vertices);

    void genTransformFeedback(void);
    int getNumGeneratedElementsPerPrimitive(void) const;
    int getNumGeneratedPrimitives(void) const;
    int getNumTessellatedPrimitives(void) const;
    int getGeometryAmplification(void) const;

    std::string getVertexSource(void) const;
    std::string getFragmentSource(void) const;
    std::string getTessellationControlSource(void) const;
    std::string getTessellationEvaluationSource(void) const;
    std::string getGeometrySource(void) const;

    static const char *getTessellationOutputDescription(TessellationOutputType tessellationOutput,
                                                        TessellationPointMode tessellationPointMode);
    static const char *getGeometryInputDescription(TessellationOutputType tessellationOutput,
                                                   TessellationPointMode tessellationPointMode);
    static const char *getGeometryOutputDescription(GeometryOutputType geometryOutput);
    glw::GLenum getOutputPrimitiveGLType(void) const;

    enum
    {
        RENDER_SIZE = 128,
    };

    const TessellationOutputType m_tessellationOutput;
    const TessellationPointMode m_tessellationPointMode;
    const GeometryOutputType m_geometryOutputType;

    glu::ShaderProgram *m_feedbackProgram;
    glu::ShaderProgram *m_nonFeedbackProgram;
    uint32_t m_patchBuffer;
    uint32_t m_feedbackID;
    uint32_t m_feedbackBuffer;
};

FeedbackPrimitiveTypeCase::FeedbackPrimitiveTypeCase(Context &context, const char *name, const char *description,
                                                     TessellationOutputType tessellationOutput,
                                                     TessellationPointMode tessellationPointMode,
                                                     GeometryOutputType geometryOutputType)
    : TestCase(context, name, description)
    , m_tessellationOutput(tessellationOutput)
    , m_tessellationPointMode(tessellationPointMode)
    , m_geometryOutputType(geometryOutputType)
    , m_feedbackProgram(DE_NULL)
    , m_nonFeedbackProgram(DE_NULL)
    , m_patchBuffer(0)
    , m_feedbackID(0)
    , m_feedbackBuffer(0)
{
    DE_ASSERT(tessellationOutput < TESSELLATION_OUT_LAST);
    DE_ASSERT(tessellationPointMode < TESSELLATION_POINTMODE_LAST);
    DE_ASSERT(geometryOutputType < GEOMETRY_OUTPUT_LAST);
}

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

void FeedbackPrimitiveTypeCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    // Requirements
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
                                !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");

    if (m_context.getRenderTarget().getWidth() < RENDER_SIZE || m_context.getRenderTarget().getHeight() < RENDER_SIZE)
        throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
                                     de::toString<int>(RENDER_SIZE) + " or larger render target.");

    // Log

    m_testCtx.getLog() << tcu::TestLog::Message << "Testing "
                       << getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode) << "->"
                       << getGeometryInputDescription(m_tessellationOutput, m_tessellationPointMode)
                       << " primitive conversion with and without transform feedback.\n"
                       << "Sending a patch of 4 vertices (2x2 uniform grid) to tessellation control shader.\n"
                       << "Control shader emits a patch of 9 vertices (3x3 uniform grid).\n"
                       << "Setting outer tessellation level = 3, inner = 3.\n"
                       << "Primitive generator emits "
                       << getTessellationOutputDescription(m_tessellationOutput, m_tessellationPointMode) << "\n"
                       << "Geometry shader transforms emitted primitives to "
                       << getGeometryOutputDescription(m_geometryOutputType) << "\n"
                       << "Reading back vertex positions of generated primitives using transform feedback.\n"
                       << "Verifying rendered image and feedback vertices are consistent.\n"
                       << "Rendering scene again with identical shader program, but without setting feedback varying. "
                          "Expecting similar output image."
                       << tcu::TestLog::EndMessage;

    // Resources

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

        gl.genBuffers(1, &m_patchBuffer);
        gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
        gl.bufferData(GL_ARRAY_BUFFER, sizeof(patchBufferData), patchBufferData, GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen buffer");
    }

    m_feedbackProgram = new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
                              << glu::TessellationControlSource(getTessellationControlSource())
                              << glu::TessellationEvaluationSource(getTessellationEvaluationSource())
                              << glu::GeometrySource(getGeometrySource())
                              << glu::TransformFeedbackVarying("tf_someVertexPosition")
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS));
    m_testCtx.getLog() << *m_feedbackProgram;
    if (!m_feedbackProgram->isOk())
        throw tcu::TestError("failed to build program");

    m_nonFeedbackProgram = new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
                              << glu::TessellationControlSource(getTessellationControlSource())
                              << glu::TessellationEvaluationSource(getTessellationEvaluationSource())
                              << glu::GeometrySource(getGeometrySource()));
    if (!m_nonFeedbackProgram->isOk())
    {
        m_testCtx.getLog() << *m_nonFeedbackProgram;
        throw tcu::TestError("failed to build program");
    }

    genTransformFeedback();
}

void FeedbackPrimitiveTypeCase::deinit(void)
{
    if (m_patchBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_patchBuffer);
        m_patchBuffer = 0;
    }

    if (m_feedbackBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_feedbackBuffer);
        m_feedbackBuffer = 0;
    }

    if (m_feedbackID)
    {
        m_context.getRenderContext().getFunctions().deleteTransformFeedbacks(1, &m_feedbackID);
        m_feedbackID = 0;
    }

    if (m_feedbackProgram)
    {
        delete m_feedbackProgram;
        m_feedbackProgram = DE_NULL;
    }

    if (m_nonFeedbackProgram)
    {
        delete m_nonFeedbackProgram;
        m_nonFeedbackProgram = DE_NULL;
    }
}

FeedbackPrimitiveTypeCase::IterateResult FeedbackPrimitiveTypeCase::iterate(void)
{
    tcu::Surface feedbackResult(RENDER_SIZE, RENDER_SIZE);
    tcu::Surface nonFeedbackResult(RENDER_SIZE, RENDER_SIZE);

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    // render with and without XFB
    renderWithFeedback(feedbackResult);
    renderWithoutFeedback(nonFeedbackResult);

    // compare
    {
        bool imageOk;

        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Comparing the image rendered with no transform feedback against the image rendered with "
                              "enabled transform feedback."
                           << tcu::TestLog::EndMessage;

        if (m_context.getRenderTarget().getNumSamples() > 1)
            imageOk =
                tcu::fuzzyCompare(m_testCtx.getLog(), "ImageCompare", "Image comparison", feedbackResult.getAccess(),
                                  nonFeedbackResult.getAccess(), 0.03f, tcu::COMPARE_LOG_RESULT);
        else
            imageOk = tcu::intThresholdPositionDeviationCompare(
                m_testCtx.getLog(), "ImageCompare", "Image comparison", feedbackResult.getAccess(),
                nonFeedbackResult.getAccess(), tcu::UVec4(8, 8, 8, 255), //!< threshold
                tcu::IVec3(1, 1, 0),                                     //!< 3x3 search kernel
                true, //!< fragments may end up over the viewport, just ignore them
                tcu::COMPARE_LOG_RESULT);

        if (!imageOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
    }

    return STOP;
}

void FeedbackPrimitiveTypeCase::renderWithFeedback(tcu::Surface &dst)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::VertexArray vao(m_context.getRenderContext());
    const glu::Query primitivesGeneratedQuery(m_context.getRenderContext());
    const int posLocation                   = gl.getAttribLocation(m_feedbackProgram->getProgram(), "a_position");
    const glw::GLenum feedbackPrimitiveMode = getOutputPrimitiveGLType();

    if (posLocation == -1)
        throw tcu::TestError("a_position was -1");

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering with transform feedback" << tcu::TestLog::EndMessage;

    gl.viewport(0, 0, dst.getWidth(), dst.getHeight());
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    gl.bindVertexArray(*vao);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(posLocation);
    GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");

    gl.useProgram(m_feedbackProgram->getProgram());
    GLU_EXPECT_NO_ERROR(gl.getError(), "use program");

    gl.patchParameteri(GL_PATCH_VERTICES, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");

    gl.beginQuery(GL_PRIMITIVES_GENERATED, *primitivesGeneratedQuery);
    GLU_EXPECT_NO_ERROR(gl.getError(), "begin GL_PRIMITIVES_GENERATED query");

    m_testCtx.getLog() << tcu::TestLog::Message << "Begin transform feedback with mode "
                       << glu::getPrimitiveTypeStr(feedbackPrimitiveMode) << tcu::TestLog::EndMessage;

    gl.beginTransformFeedback(feedbackPrimitiveMode);
    GLU_EXPECT_NO_ERROR(gl.getError(), "begin xfb");

    m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES"
                       << tcu::TestLog::EndMessage;

    gl.drawArrays(GL_PATCHES, 0, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");

    gl.endTransformFeedback();
    GLU_EXPECT_NO_ERROR(gl.getError(), "end xfb");

    gl.endQuery(GL_PRIMITIVES_GENERATED);
    GLU_EXPECT_NO_ERROR(gl.getError(), "end GL_PRIMITIVES_GENERATED query");

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

    // verify GL_PRIMITIVES_GENERATED
    {
        glw::GLuint primitivesGeneratedResult = 0;
        gl.getQueryObjectuiv(*primitivesGeneratedQuery, GL_QUERY_RESULT, &primitivesGeneratedResult);
        GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_PRIMITIVES_GENERATED value");

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying GL_PRIMITIVES_GENERATED, expecting "
                           << getNumGeneratedPrimitives() << tcu::TestLog::EndMessage;

        if ((int)primitivesGeneratedResult != getNumGeneratedPrimitives())
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, GL_PRIMITIVES_GENERATED was "
                               << primitivesGeneratedResult << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected GL_PRIMITIVES_GENERATED");
        }
        else
            m_testCtx.getLog() << tcu::TestLog::Message << "GL_PRIMITIVES_GENERATED valid." << tcu::TestLog::EndMessage;
    }

    // feedback
    {
        std::vector<tcu::Vec4> feedbackResults(getNumGeneratedElementsPerPrimitive() * getNumGeneratedPrimitives());
        const void *mappedPtr =
            gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
                              (glw::GLsizeiptr)(feedbackResults.size() * sizeof(tcu::Vec4)), GL_MAP_READ_BIT);
        glw::GLboolean unmapResult;

        GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange");

        m_testCtx.getLog() << tcu::TestLog::Message << "Reading transform feedback buffer." << tcu::TestLog::EndMessage;
        if (!mappedPtr)
            throw tcu::TestError("mapBufferRange returned null");

        deMemcpy(feedbackResults[0].getPtr(), mappedPtr, (int)(feedbackResults.size() * sizeof(tcu::Vec4)));

        unmapResult = gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
        GLU_EXPECT_NO_ERROR(gl.getError(), "unmapBuffer");

        if (unmapResult != GL_TRUE)
            throw tcu::TestError("unmapBuffer failed, did not return true");

        // verify transform results
        verifyFeedbackResults(feedbackResults);

        // verify feedback results are consistent with rendered image
        verifyRenderedImage(dst, feedbackResults);
    }
}

void FeedbackPrimitiveTypeCase::renderWithoutFeedback(tcu::Surface &dst)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::VertexArray vao(m_context.getRenderContext());
    const int posLocation = gl.getAttribLocation(m_nonFeedbackProgram->getProgram(), "a_position");

    if (posLocation == -1)
        throw tcu::TestError("a_position was -1");

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering without transform feedback" << tcu::TestLog::EndMessage;

    gl.viewport(0, 0, dst.getWidth(), dst.getHeight());
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    gl.bindVertexArray(*vao);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_patchBuffer);
    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(posLocation);
    GLU_EXPECT_NO_ERROR(gl.getError(), "setup attribs");

    gl.useProgram(m_nonFeedbackProgram->getProgram());
    GLU_EXPECT_NO_ERROR(gl.getError(), "use program");

    gl.patchParameteri(GL_PATCH_VERTICES, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");

    m_testCtx.getLog() << tcu::TestLog::Message << "Calling drawArrays with mode GL_PATCHES"
                       << tcu::TestLog::EndMessage;

    gl.drawArrays(GL_PATCHES, 0, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");

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

void FeedbackPrimitiveTypeCase::verifyFeedbackResults(const std::vector<tcu::Vec4> &feedbackResult)
{
    const int geometryAmplification = getGeometryAmplification();
    const int elementsPerPrimitive  = getNumGeneratedElementsPerPrimitive();
    const int errorFloodThreshold   = 8;
    int readNdx                     = 0;
    int numErrors                   = 0;

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying feedback results." << tcu::TestLog::EndMessage;

    for (int tessellatedPrimitiveNdx = 0; tessellatedPrimitiveNdx < getNumTessellatedPrimitives();
         ++tessellatedPrimitiveNdx)
    {
        const tcu::Vec4 primitiveVertex = feedbackResult[readNdx];

        // check the generated vertices are in the proper range (range: -0.4 <-> 0.4)
        {
            const float equalThreshold = 1.0e-6f;
            const bool centroidOk =
                (primitiveVertex.x() >= -0.4f - equalThreshold) && (primitiveVertex.x() <= 0.4f + equalThreshold) &&
                (primitiveVertex.y() >= -0.4f - equalThreshold) && (primitiveVertex.y() <= 0.4f + equalThreshold) &&
                (de::abs(primitiveVertex.z()) < equalThreshold) &&
                (de::abs(primitiveVertex.w() - 1.0f) < equalThreshold);

            if (!centroidOk && numErrors++ < errorFloodThreshold)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Element at index " << (readNdx)
                                   << " (tessellation invocation " << tessellatedPrimitiveNdx << ")\n"
                                   << "\texpected vertex in range: ( [-0.4, 0.4], [-0.4, 0.4], 0.0, 1.0 )\n"
                                   << "\tgot: " << primitiveVertex << tcu::TestLog::EndMessage;

                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid feedback output");

                ++readNdx;
                continue;
            }
        }

        // check all other primitives generated from this tessellated primitive have the same feedback value
        for (int generatedPrimitiveNdx = 0; generatedPrimitiveNdx < geometryAmplification; ++generatedPrimitiveNdx)
            for (int primitiveVertexNdx = 0; primitiveVertexNdx < elementsPerPrimitive; ++primitiveVertexNdx)
            {
                const tcu::Vec4 generatedElementVertex = feedbackResult[readNdx];
                const tcu::Vec4 equalThreshold(1.0e-6f);

                if (tcu::boolAny(tcu::greaterThan(tcu::abs(primitiveVertex - generatedElementVertex), equalThreshold)))
                {
                    if (numErrors++ < errorFloodThreshold)
                    {
                        m_testCtx.getLog()
                            << tcu::TestLog::Message << "Element at index " << (readNdx) << " (tessellation invocation "
                            << tessellatedPrimitiveNdx << ", geometry primitive " << generatedPrimitiveNdx
                            << ", emitted vertex " << primitiveVertexNdx << "):\n"
                            << "\tfeedback result was not contant over whole primitive.\n"
                            << "\tfirst emitted value: " << primitiveVertex << "\n"
                            << "\tcurrent emitted value:" << generatedElementVertex << "\n"
                            << tcu::TestLog::EndMessage;
                    }

                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
                                            "got multiple different feedback values for a single primitive");
                }

                readNdx++;
            }
    }

    if (numErrors > errorFloodThreshold)
        m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (numErrors - errorFloodThreshold) << " error(s)."
                           << tcu::TestLog::EndMessage;
}

static bool feedbackResultCompare(const tcu::Vec4 &a, const tcu::Vec4 &b)
{
    if (a.x() < b.x())
        return true;
    if (a.x() > b.x())
        return false;

    return a.y() < b.y();
}

void FeedbackPrimitiveTypeCase::verifyRenderedImage(const tcu::Surface &image, const std::vector<tcu::Vec4> &tfVertices)
{
    std::vector<tcu::Vec4> vertices;

    m_testCtx.getLog() << tcu::TestLog::Message << "Comparing result image against feedback results."
                       << tcu::TestLog::EndMessage;

    // Check only unique vertices
    std::unique_copy(tfVertices.begin(), tfVertices.end(), std::back_insert_iterator<std::vector<tcu::Vec4>>(vertices));
    std::sort(vertices.begin(), vertices.end(), feedbackResultCompare);
    vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end());

    // Verifying vertices recorded with feedback actually ended up on the result image
    for (int ndx = 0; ndx < (int)vertices.size(); ++ndx)
    {
        // Rasterization (of lines) may deviate by one pixel. In addition to that, allow minimal errors in rasterized position vs. feedback result.
        // This minimal error could result in a difference in rounding => allow one additional pixel in deviation

        const int rasterDeviation = 2;
        const tcu::IVec2 rasterPos((int)deFloatRound((vertices[ndx].x() * 0.5f + 0.5f) * (float)image.getWidth()),
                                   (int)deFloatRound((vertices[ndx].y() * 0.5f + 0.5f) * (float)image.getHeight()));

        // Find produced rasterization results
        bool found = false;

        for (int dy = -rasterDeviation; dy <= rasterDeviation && !found; ++dy)
            for (int dx = -rasterDeviation; dx <= rasterDeviation && !found; ++dx)
            {
                // Raster result could end up outside the viewport
                if (rasterPos.x() + dx < 0 || rasterPos.x() + dx >= image.getWidth() || rasterPos.y() + dy < 0 ||
                    rasterPos.y() + dy >= image.getHeight())
                    found = true;
                else
                {
                    const tcu::RGBA result = image.getPixel(rasterPos.x() + dx, rasterPos.y() + dy);

                    if (!isBlack(result))
                        found = true;
                }
            }

        if (!found)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Vertex " << vertices[ndx] << "\n"
                               << "\tCould not find rasterization output for vertex.\n"
                               << "\tExpected non-black pixels near " << rasterPos << tcu::TestLog::EndMessage;

            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid result image");
        }
    }
}

void FeedbackPrimitiveTypeCase::genTransformFeedback(void)
{
    const glw::Functions &gl       = m_context.getRenderContext().getFunctions();
    const int elementsPerPrimitive = getNumGeneratedElementsPerPrimitive();
    const int feedbackPrimitives   = getNumGeneratedPrimitives();
    const int feedbackElements     = elementsPerPrimitive * feedbackPrimitives;
    const std::vector<tcu::Vec4> initialBuffer(feedbackElements, tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f));

    gl.genTransformFeedbacks(1, &m_feedbackID);
    gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_feedbackID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "gen transform feedback");

    gl.genBuffers(1, &m_feedbackBuffer);
    gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_feedbackBuffer);
    gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(tcu::Vec4) * initialBuffer.size(), initialBuffer[0].getPtr(),
                  GL_STATIC_COPY);
    GLU_EXPECT_NO_ERROR(gl.getError(), "gen feedback buffer");

    gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_feedbackBuffer);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bind feedback buffer");
}

static int getTriangleNumOutputPrimitives(int tessellationLevel)
{
    if (tessellationLevel == 1)
        return 1;
    else if (tessellationLevel == 2)
        return 6;
    else
        return 3 * (2 + 2 * (tessellationLevel - 2)) + getTriangleNumOutputPrimitives(tessellationLevel - 2);
}

static int getTriangleNumOutputPrimitivesPoints(int tessellationLevel)
{
    if (tessellationLevel == 0)
        return 1;
    else if (tessellationLevel == 1)
        return 3;
    else
        return 3 + 3 * (tessellationLevel - 1) + getTriangleNumOutputPrimitivesPoints(tessellationLevel - 2);
}

int FeedbackPrimitiveTypeCase::getNumGeneratedElementsPerPrimitive(void) const
{
    if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES)
        return 3;
    else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)
        return 2;
    else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS)
        return 1;
    else
    {
        DE_ASSERT(false);
        return -1;
    }
}

int FeedbackPrimitiveTypeCase::getNumGeneratedPrimitives(void) const
{
    return getNumTessellatedPrimitives() * getGeometryAmplification();
}

int FeedbackPrimitiveTypeCase::getNumTessellatedPrimitives(void) const
{
    const int tessellationLevel = 3;

    if (m_tessellationPointMode == TESSELLATION_POINTMODE_OFF)
    {
        if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
            return getTriangleNumOutputPrimitives(tessellationLevel);
        else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
            return tessellationLevel * tessellationLevel * 2; // tessellated as triangles
        else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
            return tessellationLevel * tessellationLevel;
    }
    else if (m_tessellationPointMode == TESSELLATION_POINTMODE_ON)
    {
        if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
            return getTriangleNumOutputPrimitivesPoints(tessellationLevel);
        else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
            return (tessellationLevel + 1) * (tessellationLevel + 1);
        else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
            return tessellationLevel * (tessellationLevel + 1);
    }

    DE_ASSERT(false);
    return -1;
}

int FeedbackPrimitiveTypeCase::getGeometryAmplification(void) const
{
    const int outputAmplification = (m_geometryOutputType == GEOMETRY_OUTPUT_LINES) ? (2) : (1);
    const int numInputVertices    = (m_tessellationPointMode)                           ? (1) :
                                    (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) :
                                                                                          (3);

    return outputAmplification * numInputVertices;
}

glw::GLenum FeedbackPrimitiveTypeCase::getOutputPrimitiveGLType(void) const
{
    if (m_geometryOutputType == GEOMETRY_OUTPUT_TRIANGLES)
        return GL_TRIANGLES;
    else if (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)
        return GL_LINES;
    else if (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS)
        return GL_POINTS;
    else
    {
        DE_ASSERT(false);
        return -1;
    }
}

std::string FeedbackPrimitiveTypeCase::getVertexSource(void) const
{
    return specializeShader(s_positionVertexShader, m_context.getRenderContext().getType());
}

std::string FeedbackPrimitiveTypeCase::getFragmentSource(void) const
{
    return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType());
}

std::string FeedbackPrimitiveTypeCase::getTessellationControlSource(void) const
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout(vertices = 9) out;\n"
           "\n"
           "uniform highp float u_innerTessellationLevel;\n"
           "uniform highp float u_outerTessellationLevel;\n"
           "\n"
           "void main (void)\n"
           "{\n"
           "    if (gl_PatchVerticesIn != 4)\n"
           "        return;\n"
           "\n"
           "    // Convert input 2x2 grid to 3x3 grid\n"
           "    float xweight = float(gl_InvocationID % 3) / 2.0f;\n"
           "    float yweight = float(gl_InvocationID / 3) / 2.0f;\n"
           "\n"
           "    vec4 y0 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, yweight);\n"
           "    vec4 y1 = mix(gl_in[2].gl_Position, gl_in[3].gl_Position, yweight);\n"
           "\n"
           "    gl_out[gl_InvocationID].gl_Position = mix(y0, y1, xweight);\n"
           "\n";

    if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
        buf << "    gl_TessLevelOuter[0] = 3.0;\n"
               "    gl_TessLevelOuter[1] = 3.0;\n"
               "    gl_TessLevelOuter[2] = 3.0;\n"
               "    gl_TessLevelInner[0] = 3.0;\n";
    else if (m_tessellationOutput == TESSELLATION_OUT_QUADS)
        buf << "    gl_TessLevelOuter[0] = 3.0;\n"
               "    gl_TessLevelOuter[1] = 3.0;\n"
               "    gl_TessLevelOuter[2] = 3.0;\n"
               "    gl_TessLevelOuter[3] = 3.0;\n"
               "    gl_TessLevelInner[0] = 3.0;\n"
               "    gl_TessLevelInner[1] = 3.0;\n";
    else if (m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
        buf << "    gl_TessLevelOuter[0] = 3.0;\n"
               "    gl_TessLevelOuter[1] = 3.0;\n";
    else
        DE_ASSERT(false);

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string FeedbackPrimitiveTypeCase::getTessellationEvaluationSource(void) const
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout("
        << ((m_tessellationOutput == TESSELLATION_OUT_TRIANGLES) ? ("triangles") :
            (m_tessellationOutput == TESSELLATION_OUT_QUADS)     ? ("quads") :
                                                                   ("isolines"))
        << ((m_tessellationPointMode) ? (", point_mode") : (""))
        << ") in;\n"
           "\n"
           "out highp vec4 v_tessellationCoords;\n"
           "\n"
           "// note: No need to use precise gl_Position since we do not require gapless geometry\n"
           "void main (void)\n"
           "{\n"
           "    if (gl_PatchVerticesIn != 9)\n"
           "        return;\n"
           "\n"
           "    vec4 patchCentroid = vec4(0.0);\n"
           "    for (int ndx = 0; ndx < gl_PatchVerticesIn; ++ndx)\n"
           "        patchCentroid += gl_in[ndx].gl_Position;\n"
           "    patchCentroid /= patchCentroid.w;\n"
           "\n";

    if (m_tessellationOutput == TESSELLATION_OUT_TRIANGLES)
        buf << "    // map barycentric coords to 2d coords\n"
               "    const vec3 tessDirX = vec3( 0.4,  0.4, 0.0);\n"
               "    const vec3 tessDirY = vec3( 0.0, -0.4, 0.0);\n"
               "    const vec3 tessDirZ = vec3(-0.4,  0.4, 0.0);\n"
               "    gl_Position = patchCentroid + vec4(gl_TessCoord.x * tessDirX + gl_TessCoord.y * tessDirY + "
               "gl_TessCoord.z * tessDirZ, 0.0);\n";
    else if (m_tessellationOutput == TESSELLATION_OUT_QUADS || m_tessellationOutput == TESSELLATION_OUT_ISOLINES)
        buf << "    gl_Position = patchCentroid + vec4(gl_TessCoord.x * 0.8 - 0.4, gl_TessCoord.y * 0.8 - 0.4, 0.0, "
               "0.0);\n";
    else
        DE_ASSERT(false);

    buf << "    v_tessellationCoords = vec4(gl_TessCoord, 0.0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string FeedbackPrimitiveTypeCase::getGeometrySource(void) const
{
    const char *const geometryInputPrimitive  = (m_tessellationPointMode)                           ? ("points") :
                                                (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? ("lines") :
                                                                                                      ("triangles");
    const char *const geometryOutputPrimitive = (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? ("points") :
                                                (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)  ? ("line_strip") :
                                                                                                   ("triangle_strip");
    const int numInputVertices                = (m_tessellationPointMode)                           ? (1) :
                                                (m_tessellationOutput == TESSELLATION_OUT_ISOLINES) ? (2) :
                                                                                                      (3);
    const int numSingleVertexOutputVertices   = (m_geometryOutputType == GEOMETRY_OUTPUT_POINTS) ? (1) :
                                                (m_geometryOutputType == GEOMETRY_OUTPUT_LINES)  ? (4) :
                                                                                                   (3);
    const int numEmitVertices                 = numInputVertices * numSingleVertexOutputVertices;
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_GEOMETRY_SHADER}"
           "layout("
        << geometryInputPrimitive
        << ") in;\n"
           "layout("
        << geometryOutputPrimitive << ", max_vertices=" << numEmitVertices
        << ") out;\n"
           "\n"
           "in highp vec4 v_tessellationCoords[];\n"
           "out highp vec4 tf_someVertexPosition;\n"
           "\n"
           "void main (void)\n"
           "{\n"
           "    // Emit primitive\n"
           "    for (int ndx = 0; ndx < gl_in.length(); ++ndx)\n"
           "    {\n";

    switch (m_geometryOutputType)
    {
    case GEOMETRY_OUTPUT_POINTS:
        buf << "        // Draw point on vertex\n"
               "        gl_Position = gl_in[ndx].gl_Position;\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n";
        break;

    case GEOMETRY_OUTPUT_LINES:
        buf << "        // Draw cross on vertex\n"
               "        gl_Position = gl_in[ndx].gl_Position + vec4(-0.02, -0.02, 0.0, 0.0);\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n"
               "        gl_Position = gl_in[ndx].gl_Position + vec4( 0.02,  0.02, 0.0, 0.0);\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n"
               "        EndPrimitive();\n"
               "        gl_Position = gl_in[ndx].gl_Position + vec4( 0.02, -0.02, 0.0, 0.0);\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n"
               "        gl_Position = gl_in[ndx].gl_Position + vec4(-0.02,  0.02, 0.0, 0.0);\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n"
               "        EndPrimitive();\n";
        break;

    case GEOMETRY_OUTPUT_TRIANGLES:
        buf << "        // Draw triangle on vertex\n"
               "        gl_Position = gl_in[ndx].gl_Position + vec4(  0.00, -0.02, 0.0, 0.0);\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n"
               "        gl_Position = gl_in[ndx].gl_Position + vec4(  0.02,  0.00, 0.0, 0.0);\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n"
               "        gl_Position = gl_in[ndx].gl_Position + vec4( -0.02,  0.00, 0.0, 0.0);\n"
               "        tf_someVertexPosition = gl_in[gl_in.length() - 1].gl_Position;\n"
               "        EmitVertex();\n"
               "        EndPrimitive();\n";
        break;

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

    buf << "    }\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

const char *FeedbackPrimitiveTypeCase::getTessellationOutputDescription(TessellationOutputType tessellationOutput,
                                                                        TessellationPointMode pointMode)
{
    switch (tessellationOutput)
    {
    case TESSELLATION_OUT_TRIANGLES:
        return (pointMode) ? ("points (triangles in point mode)") : ("triangles");
    case TESSELLATION_OUT_QUADS:
        return (pointMode) ? ("points (quads in point mode)") : ("quads");
    case TESSELLATION_OUT_ISOLINES:
        return (pointMode) ? ("points (isolines in point mode)") : ("isolines");
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

const char *FeedbackPrimitiveTypeCase::getGeometryInputDescription(TessellationOutputType tessellationOutput,
                                                                   TessellationPointMode pointMode)
{
    switch (tessellationOutput)
    {
    case TESSELLATION_OUT_TRIANGLES:
        return (pointMode) ? ("points") : ("triangles");
    case TESSELLATION_OUT_QUADS:
        return (pointMode) ? ("points") : ("triangles");
    case TESSELLATION_OUT_ISOLINES:
        return (pointMode) ? ("points") : ("lines");
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

const char *FeedbackPrimitiveTypeCase::getGeometryOutputDescription(GeometryOutputType geometryOutput)
{
    switch (geometryOutput)
    {
    case GEOMETRY_OUTPUT_POINTS:
        return "points";
    case GEOMETRY_OUTPUT_LINES:
        return "lines";
    case GEOMETRY_OUTPUT_TRIANGLES:
        return "triangles";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

class PointSizeCase : public TestCase
{
public:
    enum Flags
    {
        FLAG_VERTEX_SET                  = 0x01, // !< set gl_PointSize in vertex shader
        FLAG_TESSELLATION_CONTROL_SET    = 0x02, // !< set gl_PointSize in tessellation evaluation shader
        FLAG_TESSELLATION_EVALUATION_SET = 0x04, // !< set gl_PointSize in tessellation control shader
        FLAG_TESSELLATION_ADD            = 0x08, // !< read and add to gl_PointSize in tessellation shader pair
        FLAG_TESSELLATION_DONT_SET       = 0x10, // !< don't set gl_PointSize in tessellation shader
        FLAG_GEOMETRY_SET                = 0x20, // !< set gl_PointSize in geometry shader
        FLAG_GEOMETRY_ADD                = 0x40, // !< read and add to gl_PointSize in geometry shader
        FLAG_GEOMETRY_DONT_SET           = 0x80, // !< don't set gl_PointSize in geometry shader
    };

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

    static std::string genTestCaseName(int flags);
    static std::string genTestCaseDescription(int flags);

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

    void checkExtensions(void) const;
    void checkPointSizeRequirements(void) const;

    void renderTo(tcu::Surface &dst);
    bool verifyImage(const tcu::Surface &src);
    int getExpectedPointSize(void) const;

    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;

    enum
    {
        RENDER_SIZE = 32,
    };

    const int m_flags;
    glu::ShaderProgram *m_program;
};

PointSizeCase::PointSizeCase(Context &context, const char *name, const char *description, int flags)
    : TestCase(context, name, description)
    , m_flags(flags)
    , m_program(DE_NULL)
{
}

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

std::string PointSizeCase::genTestCaseName(int flags)
{
    std::ostringstream buf;

    // join per-bit descriptions into a single string with '_' separator
    if (flags & FLAG_VERTEX_SET)
        buf << "vertex_set";
    if (flags & FLAG_TESSELLATION_CONTROL_SET)
        buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET - 1)) ? ("_") : ("")) << "control_set";
    if (flags & FLAG_TESSELLATION_EVALUATION_SET)
        buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET - 1)) ? ("_") : ("")) << "evaluation_set";
    if (flags & FLAG_TESSELLATION_ADD)
        buf << ((flags & (FLAG_TESSELLATION_ADD - 1)) ? ("_") : ("")) << "control_pass_eval_add";
    if (flags & FLAG_TESSELLATION_DONT_SET)
        buf << ((flags & (FLAG_TESSELLATION_DONT_SET - 1)) ? ("_") : ("")) << "eval_default";
    if (flags & FLAG_GEOMETRY_SET)
        buf << ((flags & (FLAG_GEOMETRY_SET - 1)) ? ("_") : ("")) << "geometry_set";
    if (flags & FLAG_GEOMETRY_ADD)
        buf << ((flags & (FLAG_GEOMETRY_ADD - 1)) ? ("_") : ("")) << "geometry_add";
    if (flags & FLAG_GEOMETRY_DONT_SET)
        buf << ((flags & (FLAG_GEOMETRY_DONT_SET - 1)) ? ("_") : ("")) << "geometry_default";

    return buf.str();
}

std::string PointSizeCase::genTestCaseDescription(int flags)
{
    std::ostringstream buf;

    // join per-bit descriptions into a single string with ", " separator
    if (flags & FLAG_VERTEX_SET)
        buf << "set point size in vertex shader";
    if (flags & FLAG_TESSELLATION_CONTROL_SET)
        buf << ((flags & (FLAG_TESSELLATION_CONTROL_SET - 1)) ? (", ") : (""))
            << "set point size in tessellation control shader";
    if (flags & FLAG_TESSELLATION_EVALUATION_SET)
        buf << ((flags & (FLAG_TESSELLATION_EVALUATION_SET - 1)) ? (", ") : (""))
            << "set point size in tessellation evaluation shader";
    if (flags & FLAG_TESSELLATION_ADD)
        buf << ((flags & (FLAG_TESSELLATION_ADD - 1)) ? (", ") : ("")) << "add to point size in tessellation shader";
    if (flags & FLAG_TESSELLATION_DONT_SET)
        buf << ((flags & (FLAG_TESSELLATION_DONT_SET - 1)) ? (", ") : (""))
            << "don't set point size in tessellation evaluation shader";
    if (flags & FLAG_GEOMETRY_SET)
        buf << ((flags & (FLAG_GEOMETRY_SET - 1)) ? (", ") : ("")) << "set point size in geometry shader";
    if (flags & FLAG_GEOMETRY_ADD)
        buf << ((flags & (FLAG_GEOMETRY_ADD - 1)) ? (", ") : ("")) << "add to point size in geometry shader";
    if (flags & FLAG_GEOMETRY_DONT_SET)
        buf << ((flags & (FLAG_GEOMETRY_DONT_SET - 1)) ? (", ") : ("")) << "don't set point size in geometry shader";

    return buf.str();
}

void PointSizeCase::init(void)
{
    checkExtensions();
    checkPointSizeRequirements();

    if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
    {
        m_context.getRenderContext().getFunctions().enable(GL_PROGRAM_POINT_SIZE);
    }

    // log

    if (m_flags & FLAG_VERTEX_SET)
        m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in vertex shader to 2.0."
                           << tcu::TestLog::EndMessage;
    if (m_flags & FLAG_TESSELLATION_CONTROL_SET)
        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Setting point size in tessellation control shader to 4.0. (And ignoring it in evaluation)."
            << tcu::TestLog::EndMessage;
    if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
        m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in tessellation evaluation shader to 4.0."
                           << tcu::TestLog::EndMessage;
    if (m_flags & FLAG_TESSELLATION_ADD)
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Reading point size in tessellation control shader and adding 2.0 to it in evaluation."
                           << tcu::TestLog::EndMessage;
    if (m_flags & FLAG_TESSELLATION_DONT_SET)
        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Not setting point size in tessellation evaluation shader (resulting in the default point size)."
            << tcu::TestLog::EndMessage;
    if (m_flags & FLAG_GEOMETRY_SET)
        m_testCtx.getLog() << tcu::TestLog::Message << "Setting point size in geometry shader to 6.0."
                           << tcu::TestLog::EndMessage;
    if (m_flags & FLAG_GEOMETRY_ADD)
        m_testCtx.getLog() << tcu::TestLog::Message << "Reading point size in geometry shader and adding 2.0."
                           << tcu::TestLog::EndMessage;
    if (m_flags & FLAG_GEOMETRY_DONT_SET)
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Not setting point size in geometry shader (resulting in the default point size)."
                           << tcu::TestLog::EndMessage;

    // program

    {
        glu::ProgramSources sources;
        sources << glu::VertexSource(genVertexSource()) << glu::FragmentSource(genFragmentSource());

        if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD |
                       FLAG_TESSELLATION_DONT_SET))
            sources << glu::TessellationControlSource(genTessellationControlSource())
                    << glu::TessellationEvaluationSource(genTessellationEvaluationSource());

        if (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET))
            sources << glu::GeometrySource(genGeometrySource());

        m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources);

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

void PointSizeCase::deinit(void)
{
    if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
    {
        m_context.getRenderContext().getFunctions().disable(GL_PROGRAM_POINT_SIZE);
    }

    delete m_program;
    m_program = DE_NULL;
}

PointSizeCase::IterateResult PointSizeCase::iterate(void)
{
    tcu::Surface resultImage(RENDER_SIZE, RENDER_SIZE);

    renderTo(resultImage);

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

    return STOP;
}

void PointSizeCase::checkExtensions(void) const
{
    glu::ContextType contextType = m_context.getRenderContext().getType();
    if (glu::contextSupports(contextType, glu::ApiType::core(4, 5)))
        return;

    std::vector<std::string> requiredExtensions;
    const bool supportsES32 = glu::contextSupports(contextType, glu::ApiType::es(3, 2));
    bool allOk              = true;

    if ((m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD |
                    FLAG_TESSELLATION_DONT_SET)) &&
        !supportsES32)
        requiredExtensions.push_back("GL_EXT_tessellation_shader");

    if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET | FLAG_TESSELLATION_ADD))
        requiredExtensions.push_back("GL_EXT_tessellation_point_size");

    if ((m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD | FLAG_GEOMETRY_DONT_SET))) && !supportsES32)
        requiredExtensions.push_back("GL_EXT_geometry_shader");

    if (m_flags & (m_flags & (FLAG_GEOMETRY_SET | FLAG_GEOMETRY_ADD)))
        requiredExtensions.push_back("GL_EXT_geometry_point_size");

    for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx)
        if (!m_context.getContextInfo().isExtensionSupported(requiredExtensions[ndx].c_str()))
            allOk = false;

    if (!allOk)
    {
        std::ostringstream extensionList;

        for (int ndx = 0; ndx < (int)requiredExtensions.size(); ++ndx)
        {
            if (ndx != 0)
                extensionList << ", ";
            extensionList << requiredExtensions[ndx];
        }

        throw tcu::NotSupportedError("Test requires {" + extensionList.str() + "} extension(s)");
    }
}

void PointSizeCase::checkPointSizeRequirements(void) const
{
    const glw::Functions &gl  = m_context.getRenderContext().getFunctions();
    float aliasedSizeRange[2] = {0.0f, 0.0f};
    const int requiredSize    = getExpectedPointSize();

    gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, aliasedSizeRange);

    if (float(requiredSize) > aliasedSizeRange[1])
        throw tcu::NotSupportedError("Test requires point size " + de::toString(requiredSize));
}

void PointSizeCase::renderTo(tcu::Surface &dst)
{
    const glw::Functions &gl      = m_context.getRenderContext().getFunctions();
    const bool tessellationActive = (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_EVALUATION_SET |
                                                FLAG_TESSELLATION_ADD | FLAG_TESSELLATION_DONT_SET)) != 0;
    const int positionLocation    = gl.getAttribLocation(m_program->getProgram(), "a_position");
    const glu::VertexArray vao(m_context.getRenderContext());

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

    if (positionLocation == -1)
        throw tcu::TestError("Attribute a_position location was -1");

    gl.viewport(0, 0, RENDER_SIZE, RENDER_SIZE);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    gl.bindVertexArray(*vao);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao");

    gl.useProgram(m_program->getProgram());
    GLU_EXPECT_NO_ERROR(gl.getError(), "use program");

    gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);

    if (tessellationActive)
    {
        gl.patchParameteri(GL_PATCH_VERTICES, 1);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");

        gl.drawArrays(GL_PATCHES, 0, 1);
        GLU_EXPECT_NO_ERROR(gl.getError(), "draw patches");
    }
    else
    {
        gl.drawArrays(GL_POINTS, 0, 1);
        GLU_EXPECT_NO_ERROR(gl.getError(), "draw points");
    }

    glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

bool PointSizeCase::verifyImage(const tcu::Surface &src)
{
    const bool MSAATarget  = (m_context.getRenderTarget().getNumSamples() > 1);
    const int expectedSize = getExpectedPointSize();

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying rendered point size. Expecting " << expectedSize
                       << " pixels." << tcu::TestLog::EndMessage;
    m_testCtx.getLog() << tcu::TestLog::Image("RenderImage", "Rendered image", src.getAccess());

    {
        bool resultAreaFound = false;
        tcu::IVec4 resultArea;

        // Find rasterization output area

        for (int y = 0; y < src.getHeight(); ++y)
            for (int x = 0; x < src.getWidth(); ++x)
            {
                if (!isBlack(src.getPixel(x, y)))
                {
                    if (!resultAreaFound)
                    {
                        // first fragment
                        resultArea      = tcu::IVec4(x, y, x + 1, y + 1);
                        resultAreaFound = true;
                    }
                    else
                    {
                        // union area
                        resultArea.x() = de::min(resultArea.x(), x);
                        resultArea.y() = de::min(resultArea.y(), y);
                        resultArea.z() = de::max(resultArea.z(), x + 1);
                        resultArea.w() = de::max(resultArea.w(), y + 1);
                    }
                }
            }

        if (!resultAreaFound)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Verification failed, could not find any point fragments."
                               << tcu::TestLog::EndMessage;
            return false;
        }

        // verify area size
        if (MSAATarget)
        {
            const tcu::IVec2 pointSize = resultArea.swizzle(2, 3) - resultArea.swizzle(0, 1);

            // MSAA: edges may be a little fuzzy
            if (de::abs(pointSize.x() - pointSize.y()) > 1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "ERROR! Rasterized point is not a square. Detected point size was " << pointSize
                                   << tcu::TestLog::EndMessage;
                return false;
            }

            // MSAA may produce larger areas, allow one pixel larger
            if (expectedSize != de::max(pointSize.x(), pointSize.y()) &&
                (expectedSize + 1) != de::max(pointSize.x(), pointSize.y()))
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize
                                   << ", got " << de::max(pointSize.x(), pointSize.y()) << tcu::TestLog::EndMessage;
                return false;
            }
        }
        else
        {
            const tcu::IVec2 pointSize = resultArea.swizzle(2, 3) - resultArea.swizzle(0, 1);

            if (pointSize.x() != pointSize.y())
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "ERROR! Rasterized point is not a square. Point size was " << pointSize
                                   << tcu::TestLog::EndMessage;
                return false;
            }

            if (pointSize.x() != expectedSize)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Point size invalid, expected " << expectedSize
                                   << ", got " << pointSize.x() << tcu::TestLog::EndMessage;
                return false;
            }
        }
    }

    return true;
}

int PointSizeCase::getExpectedPointSize(void) const
{
    int addition = 0;

    // geometry
    if (m_flags & FLAG_GEOMETRY_DONT_SET)
        return 1;
    else if (m_flags & FLAG_GEOMETRY_SET)
        return 6;
    else if (m_flags & FLAG_GEOMETRY_ADD)
        addition += 2;

    // tessellation
    if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
        return 4 + addition;
    else if (m_flags & FLAG_TESSELLATION_ADD)
        addition += 2;
    else if (m_flags & (FLAG_TESSELLATION_CONTROL_SET | FLAG_TESSELLATION_DONT_SET))
    {
        DE_ASSERT((m_flags & FLAG_GEOMETRY_ADD) == 0); // reading pointSize undefined
        return 1;
    }

    // vertex
    if (m_flags & FLAG_VERTEX_SET)
        return 2 + addition;

    // undefined
    DE_ASSERT(false);
    return -1;
}

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

    buf << "${VERSION_DECL}\n"
        << "in highp vec4 a_position;\n"
        << "void main ()\n"
        << "{\n"
        << "    gl_Position = a_position;\n";

    if (m_flags & FLAG_VERTEX_SET)
        buf << "    gl_PointSize = 2.0;\n";

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string PointSizeCase::genFragmentSource(void) const
{
    return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType());
}

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

    buf << "${VERSION_DECL}\n"
        << "${EXTENSION_TESSELATION_SHADER}"
        << ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("${EXTENSION_TESSELATION_POINT_SIZE}"))
        << "layout(vertices = 1) out;\n"
        << "void main ()\n"
        << "{\n"
        << "    gl_TessLevelOuter[0] = 3.0;\n"
        << "    gl_TessLevelOuter[1] = 3.0;\n"
        << "    gl_TessLevelOuter[2] = 3.0;\n"
        << "    gl_TessLevelInner[0] = 3.0;\n"
        << "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n";

    if (m_flags & FLAG_TESSELLATION_ADD)
        buf << "    // pass as is to eval\n"
            << "    gl_out[gl_InvocationID].gl_PointSize = gl_in[gl_InvocationID].gl_PointSize;\n";
    else if (m_flags & FLAG_TESSELLATION_CONTROL_SET)
        buf << "    // thrown away\n"
            << "    gl_out[gl_InvocationID].gl_PointSize = 4.0;\n";

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

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

    buf << "${VERSION_DECL}\n"
        << "${EXTENSION_TESSELATION_SHADER}"
        << ((m_flags & FLAG_TESSELLATION_DONT_SET) ? ("") : ("${EXTENSION_TESSELATION_POINT_SIZE}"))
        << "layout(triangles, point_mode) in;\n"
        << "void main ()\n"
        << "{\n"
        << "    // hide all but one vertex\n"
        << "    if (gl_TessCoord.x < 0.99)\n"
        << "        gl_Position = vec4(-2.0, 0.0, 0.0, 1.0);\n"
        << "    else\n"
        << "        gl_Position = gl_in[0].gl_Position;\n";

    if (m_flags & FLAG_TESSELLATION_ADD)
        buf << "\n"
            << "    // add to point size\n"
            << "    gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n";
    else if (m_flags & FLAG_TESSELLATION_EVALUATION_SET)
        buf << "\n"
            << "    // set point size\n"
            << "    gl_PointSize = 4.0;\n";

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string PointSizeCase::genGeometrySource(void) const
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
        << "${EXTENSION_GEOMETRY_SHADER}"
        << ((m_flags & FLAG_GEOMETRY_DONT_SET) ? ("") : ("${EXTENSION_GEOMETRY_POINT_SIZE}")) << "layout (points) in;\n"
        << "layout (points, max_vertices=1) out;\n"
        << "\n"
        << "void main ()\n"
        << "{\n";

    if (m_flags & FLAG_GEOMETRY_SET)
        buf << "    gl_Position = gl_in[0].gl_Position;\n"
            << "    gl_PointSize = 6.0;\n";
    else if (m_flags & FLAG_GEOMETRY_ADD)
        buf << "    gl_Position = gl_in[0].gl_Position;\n"
            << "    gl_PointSize = gl_in[0].gl_PointSize + 2.0;\n";
    else if (m_flags & FLAG_GEOMETRY_DONT_SET)
        buf << "    gl_Position = gl_in[0].gl_Position;\n";

    buf << "    EmitVertex();\n"
        << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

class AllowedRenderFailureException : public std::runtime_error
{
public:
    AllowedRenderFailureException(const char *message) : std::runtime_error(message)
    {
    }
};

class GridRenderCase : public TestCase
{
public:
    enum Flags
    {
        FLAG_TESSELLATION_MAX_SPEC                   = 0x0001,
        FLAG_TESSELLATION_MAX_IMPLEMENTATION         = 0x0002,
        FLAG_GEOMETRY_MAX_SPEC                       = 0x0004,
        FLAG_GEOMETRY_MAX_IMPLEMENTATION             = 0x0008,
        FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC           = 0x0010,
        FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION = 0x0020,

        FLAG_GEOMETRY_SCATTER_INSTANCES  = 0x0040,
        FLAG_GEOMETRY_SCATTER_PRIMITIVES = 0x0080,
        FLAG_GEOMETRY_SEPARATE_PRIMITIVES =
            0x0100, //!< if set, geometry shader outputs separate grid cells and not continuous slices
        FLAG_GEOMETRY_SCATTER_LAYERS = 0x0200,

        FLAG_ALLOW_OUT_OF_MEMORY = 0x0400, //!< allow draw command to set GL_OUT_OF_MEMORY
    };

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

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

    void renderTo(std::vector<tcu::Surface> &dst);
    bool verifyResultLayer(int layerNdx, const tcu::Surface &dst);

    std::string getVertexSource(void);
    std::string getFragmentSource(void);
    std::string getTessellationControlSource(int tessLevel);
    std::string getTessellationEvaluationSource(int tessLevel);
    std::string getGeometryShaderSource(int numPrimitives, int numInstances, int tessLevel);

    enum
    {
        RENDER_SIZE = 256
    };

    std::string m_description;

    const int m_flags;

    glu::ShaderProgram *m_program;
    uint32_t m_texture;
    int m_numLayers;
};

GridRenderCase::GridRenderCase(Context &context, const char *name, const char *description, int flags)
    : TestCase(context, name, description)
    , m_description(description)
    , m_flags(flags)
    , m_program(DE_NULL)
    , m_texture(0)
    , m_numLayers(1)
{
    DE_ASSERT(((m_flags & FLAG_TESSELLATION_MAX_SPEC) == 0) || ((m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION) == 0));
    DE_ASSERT(((m_flags & FLAG_GEOMETRY_MAX_SPEC) == 0) || ((m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION) == 0));
    DE_ASSERT(((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC) == 0) ||
              ((m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION) == 0));
    DE_ASSERT(((m_flags & (FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS)) != 0) ==
              ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0));
}

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

void GridRenderCase::init(void)
{
    const glw::Functions &gl      = m_context.getRenderContext().getFunctions();
    glu::ContextType contextType  = m_context.getRenderContext().getType();
    const bool supportsES32orGL45 = glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
                                    glu::contextSupports(contextType, glu::ApiType::core(4, 5));

    // Requirements

    if (!supportsES32orGL45 && (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
                                !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");

    if ((m_flags & FLAG_GEOMETRY_SCATTER_LAYERS) == 0)
    {
        if (m_context.getRenderTarget().getWidth() < RENDER_SIZE ||
            m_context.getRenderTarget().getHeight() < RENDER_SIZE)
            throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_SIZE) + "x" +
                                         de::toString<int>(RENDER_SIZE) + " or larger render target.");
    }

    // Log

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Testing tessellation and geometry shaders that output a large number of primitives.\n"
                       << m_description << tcu::TestLog::EndMessage;

    // Render target
    if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
    {
        // set limits
        m_numLayers = 8;

        m_testCtx.getLog() << tcu::TestLog::Message << "Rendering to 2d texture array, numLayers = " << m_numLayers
                           << tcu::TestLog::EndMessage;

        gl.genTextures(1, &m_texture);
        gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texture);
        gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, RENDER_SIZE, RENDER_SIZE, m_numLayers);

        gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        GLU_EXPECT_NO_ERROR(gl.getError(), "gen texture");
    }

    // Gen program
    {
        glu::ProgramSources sources;
        int tessGenLevel = -1;

        sources << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource());

        // Tessellation limits
        {
            if (m_flags & FLAG_TESSELLATION_MAX_IMPLEMENTATION)
            {
                gl.getIntegerv(GL_MAX_TESS_GEN_LEVEL, &tessGenLevel);
                GLU_EXPECT_NO_ERROR(gl.getError(), "query tessellation limits");
            }
            else if (m_flags & FLAG_TESSELLATION_MAX_SPEC)
            {
                tessGenLevel = 64;
            }
            else
            {
                tessGenLevel = 5;
            }

            m_testCtx.getLog() << tcu::TestLog::Message << "Tessellation level: " << tessGenLevel << ", mode = quad.\n"
                               << "\tEach input patch produces " << (tessGenLevel * tessGenLevel) << " ("
                               << (tessGenLevel * tessGenLevel * 2) << " triangles)\n"
                               << tcu::TestLog::EndMessage;

            sources << glu::TessellationControlSource(getTessellationControlSource(tessGenLevel))
                    << glu::TessellationEvaluationSource(getTessellationEvaluationSource(tessGenLevel));
        }

        // Geometry limits
        {
            int geometryOutputComponents      = -1;
            int geometryOutputVertices        = -1;
            int geometryTotalOutputComponents = -1;
            int geometryShaderInvocations     = -1;
            bool logGeometryLimits            = false;
            bool logInvocationLimits          = false;

            if (m_flags & FLAG_GEOMETRY_MAX_IMPLEMENTATION)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Using implementation maximum geometry shader output limits."
                                   << tcu::TestLog::EndMessage;

                gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_COMPONENTS, &geometryOutputComponents);
                gl.getIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &geometryOutputVertices);
                gl.getIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &geometryTotalOutputComponents);
                GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry limits");

                logGeometryLimits = true;
            }
            else if (m_flags & FLAG_GEOMETRY_MAX_SPEC)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Using geometry shader extension minimum maximum output limits."
                                   << tcu::TestLog::EndMessage;

                geometryOutputComponents      = 128;
                geometryOutputVertices        = 256;
                geometryTotalOutputComponents = 1024;
                logGeometryLimits             = true;
            }
            else
            {
                geometryOutputComponents      = 128;
                geometryOutputVertices        = 16;
                geometryTotalOutputComponents = 1024;
            }

            if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION)
            {
                gl.getIntegerv(GL_MAX_GEOMETRY_SHADER_INVOCATIONS, &geometryShaderInvocations);
                GLU_EXPECT_NO_ERROR(gl.getError(), "query geometry invocation limits");

                logInvocationLimits = true;
            }
            else if (m_flags & FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC)
            {
                geometryShaderInvocations = 32;
                logInvocationLimits       = true;
            }
            else
            {
                geometryShaderInvocations = 4;
            }

            if (logGeometryLimits || logInvocationLimits)
            {
                tcu::MessageBuilder msg(&m_testCtx.getLog());

                msg << "Geometry shader, targeting following limits:\n";

                if (logGeometryLimits)
                    msg << "\tGL_MAX_GEOMETRY_OUTPUT_COMPONENTS = " << geometryOutputComponents << "\n"
                        << "\tGL_MAX_GEOMETRY_OUTPUT_VERTICES = " << geometryOutputVertices << "\n"
                        << "\tGL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = " << geometryTotalOutputComponents << "\n";

                if (logInvocationLimits)
                    msg << "\tGL_MAX_GEOMETRY_SHADER_INVOCATIONS = " << geometryShaderInvocations;

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

            {
                const bool separatePrimitives    = (m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) != 0;
                const int numComponentsPerVertex = 8; // vec4 pos, vec4 color
                int numVerticesPerInvocation;
                int numPrimitivesPerInvocation;
                int geometryVerticesPerPrimitive;
                int geometryPrimitivesOutPerPrimitive;

                if (separatePrimitives)
                {
                    const int numComponentLimit = geometryTotalOutputComponents / (4 * numComponentsPerVertex);
                    const int numOutputLimit    = geometryOutputVertices / 4;

                    numPrimitivesPerInvocation = de::min(numComponentLimit, numOutputLimit);
                    numVerticesPerInvocation   = numPrimitivesPerInvocation * 4;
                }
                else
                {
                    // If FLAG_GEOMETRY_SEPARATE_PRIMITIVES is not set, geometry shader fills a rectangle area in slices.
                    // Each slice is a triangle strip and is generated by a single shader invocation.
                    // One slice with 4 segment ends (nodes) and 3 segments:
                    //    .__.__.__.
                    //    |\ |\ |\ |
                    //    |_\|_\|_\|

                    const int numSliceNodesComponentLimit =
                        geometryTotalOutputComponents / (2 * numComponentsPerVertex); // each node 2 vertices
                    const int numSliceNodesOutputLimit = geometryOutputVertices / 2;  // each node 2 vertices
                    const int numSliceNodes            = de::min(numSliceNodesComponentLimit, numSliceNodesOutputLimit);

                    numVerticesPerInvocation   = numSliceNodes * 2;
                    numPrimitivesPerInvocation = (numSliceNodes - 1) * 2;
                }

                geometryVerticesPerPrimitive      = numVerticesPerInvocation * geometryShaderInvocations;
                geometryPrimitivesOutPerPrimitive = numPrimitivesPerInvocation * geometryShaderInvocations;

                m_testCtx.getLog() << tcu::TestLog::Message << "Geometry shader:\n"
                                   << "\tTotal output vertex count per invocation: " << (numVerticesPerInvocation)
                                   << "\n"
                                   << "\tTotal output primitive count per invocation: " << (numPrimitivesPerInvocation)
                                   << "\n"
                                   << "\tNumber of invocations per primitive: " << geometryShaderInvocations << "\n"
                                   << "\tTotal output vertex count per input primitive: "
                                   << (geometryVerticesPerPrimitive) << "\n"
                                   << "\tTotal output primitive count per input primitive: "
                                   << (geometryPrimitivesOutPerPrimitive) << "\n"
                                   << tcu::TestLog::EndMessage;

                sources << glu::GeometrySource(
                    getGeometryShaderSource(numPrimitivesPerInvocation, geometryShaderInvocations, tessGenLevel));

                m_testCtx.getLog() << tcu::TestLog::Message << "Program:\n"
                                   << "\tTotal program output vertices count per input patch: "
                                   << (tessGenLevel * tessGenLevel * 2 * geometryVerticesPerPrimitive) << "\n"
                                   << "\tTotal program output primitive count per input patch: "
                                   << (tessGenLevel * tessGenLevel * 2 * geometryPrimitivesOutPerPrimitive) << "\n"
                                   << tcu::TestLog::EndMessage;
            }
        }

        m_program = new glu::ShaderProgram(m_context.getRenderContext(), sources);
        m_testCtx.getLog() << *m_program;
        if (!m_program->isOk())
            throw tcu::TestError("failed to build program");
    }
}

void GridRenderCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;

    if (m_texture)
    {
        m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
        m_texture = 0;
    }
}

GridRenderCase::IterateResult GridRenderCase::iterate(void)
{
    std::vector<tcu::Surface> renderedLayers(m_numLayers);
    bool allLayersOk = true;

    for (int ndx = 0; ndx < m_numLayers; ++ndx)
        renderedLayers[ndx].setSize(RENDER_SIZE, RENDER_SIZE);

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Rendering single point at the origin. Expecting yellow and green colored grid-like image. "
                          "(High-frequency grid may appear unicolored)."
                       << tcu::TestLog::EndMessage;

    try
    {
        renderTo(renderedLayers);
    }
    catch (const AllowedRenderFailureException &ex)
    {
        // Got accepted failure
        m_testCtx.getLog() << tcu::TestLog::Message << "Could not render, reason: " << ex.what() << "\n"
                           << "Failure is allowed." << tcu::TestLog::EndMessage;

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

    for (int ndx = 0; ndx < m_numLayers; ++ndx)
        allLayersOk &= verifyResultLayer(ndx, renderedLayers[ndx]);

    if (allLayersOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
    return STOP;
}

void GridRenderCase::renderTo(std::vector<tcu::Surface> &dst)
{
    const glw::Functions &gl   = m_context.getRenderContext().getFunctions();
    const int positionLocation = gl.getAttribLocation(m_program->getProgram(), "a_position");
    const glu::VertexArray vao(m_context.getRenderContext());
    de::MovePtr<glu::Framebuffer> fbo;

    if (positionLocation == -1)
        throw tcu::TestError("Attribute a_position location was -1");

    gl.viewport(0, 0, dst.front().getWidth(), dst.front().getHeight());
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    GLU_EXPECT_NO_ERROR(gl.getError(), "viewport");

    gl.bindVertexArray(*vao);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bind vao");

    gl.useProgram(m_program->getProgram());
    GLU_EXPECT_NO_ERROR(gl.getError(), "use program");

    gl.patchParameteri(GL_PATCH_VERTICES, 1);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");

    gl.vertexAttrib4f(positionLocation, 0.0f, 0.0f, 0.0f, 1.0f);

    if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
    {
        // clear texture contents
        {
            glu::Framebuffer clearFbo(m_context.getRenderContext());
            gl.bindFramebuffer(GL_FRAMEBUFFER, *clearFbo);

            for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
            {
                gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx);
                gl.clear(GL_COLOR_BUFFER_BIT);
            }

            GLU_EXPECT_NO_ERROR(gl.getError(), "clear tex contents");
        }

        // create and bind layered fbo

        fbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));

        gl.bindFramebuffer(GL_FRAMEBUFFER, **fbo);
        gl.framebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");
    }
    else
    {
        // clear viewport
        gl.clear(GL_COLOR_BUFFER_BIT);
    }

    // draw
    {
        glw::GLenum glerror;

        gl.drawArrays(GL_PATCHES, 0, 1);

        glerror = gl.getError();
        if (glerror == GL_OUT_OF_MEMORY && (m_flags & FLAG_ALLOW_OUT_OF_MEMORY))
            throw AllowedRenderFailureException("got GL_OUT_OF_MEMORY while drawing");

        GLU_EXPECT_NO_ERROR(glerror, "draw patches");
    }

    // Read layers

    if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
    {
        glu::Framebuffer readFbo(m_context.getRenderContext());
        gl.bindFramebuffer(GL_FRAMEBUFFER, *readFbo);

        for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
        {
            gl.framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0, layerNdx);
            glu::readPixels(m_context.getRenderContext(), 0, 0, dst[layerNdx].getAccess());
            GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
        }
    }
    else
    {
        glu::readPixels(m_context.getRenderContext(), 0, 0, dst.front().getAccess());
        GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
    }
}

bool GridRenderCase::verifyResultLayer(int layerNdx, const tcu::Surface &image)
{
    tcu::Surface errorMask(image.getWidth(), image.getHeight());
    bool foundError = false;

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

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output layer " << layerNdx << tcu::TestLog::EndMessage;

    for (int y = 0; y < image.getHeight(); ++y)
        for (int x = 0; x < image.getWidth(); ++x)
        {
            const int threshold   = 8;
            const tcu::RGBA color = image.getPixel(x, y);

            // Color must be a linear combination of green and yellow
            if (color.getGreen() < 255 - threshold || color.getBlue() > threshold)
            {
                errorMask.setPixel(x, y, tcu::RGBA::red());
                foundError = true;
            }
        }

    if (!foundError)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image valid." << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("ImageVerification", "Image verification")
                           << tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
                           << tcu::TestLog::EndImageSet;
        return true;
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed, found invalid pixels."
                           << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("ImageVerification", "Image verification")
                           << tcu::TestLog::Image("Result", "Rendered result", image.getAccess())
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
                           << tcu::TestLog::EndImageSet;
        return false;
    }
}

std::string GridRenderCase::getVertexSource(void)
{
    return specializeShader(s_positionVertexShader, m_context.getRenderContext().getType());
}

std::string GridRenderCase::getFragmentSource(void)
{
    const char *source = "${VERSION_DECL}\n"
                         "flat in mediump vec4 v_color;\n"
                         "layout(location = 0) out mediump vec4 fragColor;\n"
                         "void main (void)\n"
                         "{\n"
                         "    fragColor = v_color;\n"
                         "}\n";

    return specializeShader(source, m_context.getRenderContext().getType());
}

std::string GridRenderCase::getTessellationControlSource(int tessLevel)
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout(vertices=1) out;\n"
           "\n"
           "void main()\n"
           "{\n"
           "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
           "    gl_TessLevelOuter[0] = "
        << tessLevel
        << ".0;\n"
           "    gl_TessLevelOuter[1] = "
        << tessLevel
        << ".0;\n"
           "    gl_TessLevelOuter[2] = "
        << tessLevel
        << ".0;\n"
           "    gl_TessLevelOuter[3] = "
        << tessLevel
        << ".0;\n"
           "    gl_TessLevelInner[0] = "
        << tessLevel
        << ".0;\n"
           "    gl_TessLevelInner[1] = "
        << tessLevel
        << ".0;\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string GridRenderCase::getTessellationEvaluationSource(int tessLevel)
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_TESSELATION_SHADER}"
           "layout(quads) in;\n"
           "\n"
           "out mediump ivec2 v_tessellationGridPosition;\n"
           "\n"
           "// note: No need to use precise gl_Position since position does not depend on order\n"
           "void main (void)\n"
           "{\n";

    if (m_flags & (FLAG_GEOMETRY_SCATTER_INSTANCES | FLAG_GEOMETRY_SCATTER_PRIMITIVES | FLAG_GEOMETRY_SCATTER_LAYERS))
        buf << "    // Cover only a small area in a corner. The area will be expanded in geometry shader to cover "
               "whole viewport\n"
               "    gl_Position = vec4(gl_TessCoord.x * 0.3 - 1.0, gl_TessCoord.y * 0.3 - 1.0, 0.0, 1.0);\n";
    else
        buf << "    // Fill the whole viewport\n"
               "    gl_Position = vec4(gl_TessCoord.x * 2.0 - 1.0, gl_TessCoord.y * 2.0 - 1.0, 0.0, 1.0);\n";

    buf << "    // Calculate position in tessellation grid\n"
           "    v_tessellationGridPosition = ivec2(round(gl_TessCoord.xy * float("
        << tessLevel
        << ")));\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string GridRenderCase::getGeometryShaderSource(int numPrimitives, int numInstances, int tessLevel)
{
    std::ostringstream buf;

    buf << "${VERSION_DECL}\n"
           "${EXTENSION_GEOMETRY_SHADER}"
           "layout(triangles, invocations="
        << numInstances
        << ") in;\n"
           "layout(triangle_strip, max_vertices="
        << ((m_flags & FLAG_GEOMETRY_SEPARATE_PRIMITIVES) ? (4 * numPrimitives) : (numPrimitives + 2))
        << ") out;\n"
           "\n"
           "in mediump ivec2 v_tessellationGridPosition[];\n"
           "flat out highp vec4 v_color;\n"
           "\n"
           "void main ()\n"
           "{\n"
           "    const float equalThreshold = 0.001;\n"
           "    const float gapOffset = 0.0001; // subdivision performed by the geometry shader might produce gaps. "
           "Fill potential gaps by enlarging the output slice a little.\n"
           "\n"
           "    // Input triangle is generated from an axis-aligned rectangle by splitting it in half\n"
           "    // Original rectangle can be found by finding the bounding AABB of the triangle\n"
           "    vec4 aabb = vec4(min(gl_in[0].gl_Position.x, min(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
           "                     min(gl_in[0].gl_Position.y, min(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)),\n"
           "                     max(gl_in[0].gl_Position.x, max(gl_in[1].gl_Position.x, gl_in[2].gl_Position.x)),\n"
           "                     max(gl_in[0].gl_Position.y, max(gl_in[1].gl_Position.y, gl_in[2].gl_Position.y)));\n"
           "\n"
           "    // Location in tessellation grid\n"
           "    ivec2 gridPosition = ivec2(min(v_tessellationGridPosition[0], min(v_tessellationGridPosition[1], "
           "v_tessellationGridPosition[2])));\n"
           "\n"
           "    // Which triangle of the two that split the grid cell\n"
           "    int numVerticesOnBottomEdge = 0;\n"
           "    for (int ndx = 0; ndx < 3; ++ndx)\n"
           "        if (abs(gl_in[ndx].gl_Position.y - aabb.w) < equalThreshold)\n"
           "            ++numVerticesOnBottomEdge;\n"
           "    bool isBottomTriangle = numVerticesOnBottomEdge == 2;\n"
           "\n";

    if (m_flags & FLAG_GEOMETRY_SCATTER_PRIMITIVES)
    {
        // scatter primitives
        buf << "    // Draw grid cells\n"
               "    int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
               "    for (int ndx = 0; ndx < "
            << numPrimitives
            << "; ++ndx)\n"
               "    {\n"
               "        ivec2 dstGridSize = ivec2("
            << tessLevel << " * " << numPrimitives << ", 2 * " << tessLevel << " * " << numInstances
            << ");\n"
               "        ivec2 dstGridNdx = ivec2("
            << tessLevel << " * ndx + gridPosition.x, " << tessLevel
            << " * inputTriangleNdx + 2 * gridPosition.y + ndx * 127) % dstGridSize;\n"
               "        vec4 dstArea;\n"
               "        dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n"
               "        dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n"
               "        dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n"
               "        dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n"
               "\n"
               "        vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "        vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
               "        vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n"
               "\n"
               "        gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        EmitVertex();\n"
               "        EndPrimitive();\n"
               "    }\n";
    }
    else if (m_flags & FLAG_GEOMETRY_SCATTER_LAYERS)
    {
        // Number of subrectangle instances = num layers
        DE_ASSERT(m_numLayers == numInstances * 2);

        buf << "    // Draw grid cells, send each primitive to a separate layer\n"
               "    int baseLayer = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
               "    for (int ndx = 0; ndx < "
            << numPrimitives
            << "; ++ndx)\n"
               "    {\n"
               "        ivec2 dstGridSize = ivec2("
            << tessLevel << " * " << numPrimitives << ", " << tessLevel
            << ");\n"
               "        ivec2 dstGridNdx = ivec2((gridPosition.x * "
            << numPrimitives
            << " * 7 + ndx)*13, (gridPosition.y * 127 + ndx) * 19) % dstGridSize;\n"
               "        vec4 dstArea;\n"
               "        dstArea.x = float(dstGridNdx.x) / float(dstGridSize.x) * 2.0 - 1.0 - gapOffset;\n"
               "        dstArea.y = float(dstGridNdx.y) / float(dstGridSize.y) * 2.0 - 1.0 - gapOffset;\n"
               "        dstArea.z = float(dstGridNdx.x+1) / float(dstGridSize.x) * 2.0 - 1.0 + gapOffset;\n"
               "        dstArea.w = float(dstGridNdx.y+1) / float(dstGridSize.y) * 2.0 - 1.0 + gapOffset;\n"
               "\n"
               "        vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "        vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
               "        vec4 outputColor = (((dstGridNdx.y + dstGridNdx.x) % 2) == 0) ? (green) : (yellow);\n"
               "\n"
               "        gl_Position = vec4(dstArea.x, dstArea.y, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        gl_Layer = ((baseLayer + ndx) * 11) % "
            << m_numLayers
            << ";\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = vec4(dstArea.x, dstArea.w, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        gl_Layer = ((baseLayer + ndx) * 11) % "
            << m_numLayers
            << ";\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = vec4(dstArea.z, dstArea.y, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        gl_Layer = ((baseLayer + ndx) * 11) % "
            << m_numLayers
            << ";\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = vec4(dstArea.z, dstArea.w, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        gl_Layer = ((baseLayer + ndx) * 11) % "
            << m_numLayers
            << ";\n"
               "        EmitVertex();\n"
               "        EndPrimitive();\n"
               "    }\n";
    }
    else
    {
        if (m_flags & FLAG_GEOMETRY_SCATTER_INSTANCES)
        {
            buf << "    // Scatter slices\n"
                   "    int inputTriangleNdx = gl_InvocationID * 2 + ((isBottomTriangle) ? (1) : (0));\n"
                   "    ivec2 srcSliceNdx = ivec2(gridPosition.x, gridPosition.y * "
                << (numInstances * 2)
                << " + inputTriangleNdx);\n"
                   "    ivec2 dstSliceNdx = ivec2(7 * srcSliceNdx.x, 127 * srcSliceNdx.y) % ivec2("
                << tessLevel << ", " << tessLevel << " * " << (numInstances * 2)
                << ");\n"
                   "\n"
                   "    // Draw slice to the dstSlice slot\n"
                   "    vec4 outputSliceArea;\n"
                   "    outputSliceArea.x = float(dstSliceNdx.x) / float("
                << tessLevel
                << ") * 2.0 - 1.0 - gapOffset;\n"
                   "    outputSliceArea.y = float(dstSliceNdx.y) / float("
                << (tessLevel * numInstances * 2)
                << ") * 2.0 - 1.0 - gapOffset;\n"
                   "    outputSliceArea.z = float(dstSliceNdx.x+1) / float("
                << tessLevel
                << ") * 2.0 - 1.0 + gapOffset;\n"
                   "    outputSliceArea.w = float(dstSliceNdx.y+1) / float("
                << (tessLevel * numInstances * 2) << ") * 2.0 - 1.0 + gapOffset;\n";
        }
        else
        {
            buf << "    // Fill the input area with slices\n"
                   "    // Upper triangle produces slices only to the upper half of the quad and vice-versa\n"
                   "    float triangleOffset = (isBottomTriangle) ? ((aabb.w + aabb.y) / 2.0) : (aabb.y);\n"
                   "    // Each slice is a invocation\n"
                   "    float sliceHeight = (aabb.w - aabb.y) / float(2 * "
                << numInstances
                << ");\n"
                   "    float invocationOffset = float(gl_InvocationID) * sliceHeight;\n"
                   "\n"
                   "    vec4 outputSliceArea;\n"
                   "    outputSliceArea.x = aabb.x - gapOffset;\n"
                   "    outputSliceArea.y = triangleOffset + invocationOffset - gapOffset;\n"
                   "    outputSliceArea.z = aabb.z + gapOffset;\n"
                   "    outputSliceArea.w = triangleOffset + invocationOffset + sliceHeight + gapOffset;\n";
        }

        buf << "\n"
               "    // Draw slice\n"
               "    for (int ndx = 0; ndx < "
            << ((numPrimitives + 2) / 2)
            << "; ++ndx)\n"
               "    {\n"
               "        vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "        vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
               "        vec4 outputColor = (((gl_InvocationID + ndx) % 2) == 0) ? (green) : (yellow);\n"
               "        float xpos = mix(outputSliceArea.x, outputSliceArea.z, float(ndx) / float("
            << (numPrimitives / 2)
            << "));\n"
               "\n"
               "        gl_Position = vec4(xpos, outputSliceArea.y, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        EmitVertex();\n"
               "\n"
               "        gl_Position = vec4(xpos, outputSliceArea.w, 0.0, 1.0);\n"
               "        v_color = outputColor;\n"
               "        EmitVertex();\n"
               "    }\n";
    }

    buf << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

class FeedbackRecordVariableSelectionCase : public TestCase
{
public:
    FeedbackRecordVariableSelectionCase(Context &context, const char *name, const char *description);
    ~FeedbackRecordVariableSelectionCase(void);

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

    std::string getVertexSource(void);
    std::string getFragmentSource(void);
    std::string getTessellationControlSource(void);
    std::string getTessellationEvaluationSource(void);
    std::string getGeometrySource(void);

    glu::ShaderProgram *m_program;
    uint32_t m_xfbBuf;
};

FeedbackRecordVariableSelectionCase::FeedbackRecordVariableSelectionCase(Context &context, const char *name,
                                                                         const char *description)
    : TestCase(context, name, description)
    , m_program(DE_NULL)
    , m_xfbBuf(0)
{
}

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

void FeedbackRecordVariableSelectionCase::init(void)
{
    const bool supportsES32   = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
    const bool supportsCore40 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 0));

    if ((!supportsES32 && !supportsCore40) &&
        (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
         !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader")))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader and GL_EXT_geometry_shader extensions");

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Declaring multiple output variables with the same name in multiple shader stages. Capturing "
                          "the value of the varying using transform feedback."
                       << tcu::TestLog::EndMessage;

    // gen feedback buffer fit for 1 triangle (4 components)
    {
        static const tcu::Vec4 initialData[3] = {
            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, -1.0f),
        };

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

        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Creating buffer for transform feedback. Allocating storage for one triangle. Filling with -1.0"
            << tcu::TestLog::EndMessage;

        gl.genBuffers(1, &m_xfbBuf);
        gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_xfbBuf);
        gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, (int)(sizeof(tcu::Vec4[3])), initialData, GL_DYNAMIC_READ);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen xfb buf");
    }

    // gen shader
    m_program = new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(getVertexSource()) << glu::FragmentSource(getFragmentSource())
                              << glu::TessellationControlSource(getTessellationControlSource())
                              << glu::TessellationEvaluationSource(getTessellationEvaluationSource())
                              << glu::GeometrySource(getGeometrySource())
                              << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS)
                              << glu::TransformFeedbackVarying("tf_feedback"));
    m_testCtx.getLog() << *m_program;

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

void FeedbackRecordVariableSelectionCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;

    if (m_xfbBuf)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_xfbBuf);
        m_xfbBuf = 0;
    }
}

FeedbackRecordVariableSelectionCase::IterateResult FeedbackRecordVariableSelectionCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const int posLoc         = gl.getAttribLocation(m_program->getProgram(), "a_position");
    const glu::VertexArray vao(m_context.getRenderContext());

    if (posLoc == -1)
        throw tcu::TestError("a_position attribute location was -1");

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering a patch of size 3." << tcu::TestLog::EndMessage;

    // Render and feed back

    gl.viewport(0, 0, 1, 1);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    gl.bindVertexArray(*vao);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray");

    gl.vertexAttrib4f(posLoc, 0.0f, 0.0f, 0.0f, 1.0f);
    GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttrib4f");

    gl.useProgram(m_program->getProgram());
    GLU_EXPECT_NO_ERROR(gl.getError(), "use program");

    gl.patchParameteri(GL_PATCH_VERTICES, 3);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set patch param");

    gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_xfbBuf);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bind xfb buf");

    gl.beginTransformFeedback(GL_TRIANGLES);
    GLU_EXPECT_NO_ERROR(gl.getError(), "beginTransformFeedback");

    gl.drawArrays(GL_PATCHES, 0, 3);
    GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");

    gl.endTransformFeedback();
    GLU_EXPECT_NO_ERROR(gl.getError(), "beginTransformFeedback");

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Verifying the value of tf_feedback using transform feedback, expecting (3.0, 3.0, 3.0, 3.0)."
                       << tcu::TestLog::EndMessage;

    // Read back result (one triangle)
    {
        tcu::Vec4 feedbackValues[3];
        const void *mapPtr =
            gl.mapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, (int)sizeof(feedbackValues), GL_MAP_READ_BIT);
        GLU_EXPECT_NO_ERROR(gl.getError(), "mapBufferRange");

        if (mapPtr == DE_NULL)
            throw tcu::TestError("mapBufferRange returned null");

        deMemcpy(feedbackValues, mapPtr, sizeof(feedbackValues));

        if (gl.unmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER) != GL_TRUE)
            throw tcu::TestError("unmapBuffer did not return TRUE");

        for (int ndx = 0; ndx < 3; ++ndx)
        {
            if (!tcu::boolAll(tcu::lessThan(tcu::abs(feedbackValues[ndx] - tcu::Vec4(3.0f)), tcu::Vec4(0.001f))))
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Feedback vertex " << ndx
                                   << ": expected (3.0, 3.0, 3.0, 3.0), got " << feedbackValues[ndx]
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected feedback results");
            }
        }
    }

    return STOP;
}

std::string FeedbackRecordVariableSelectionCase::getVertexSource(void)
{
    std::string source = "${VERSION_DECL}\n"
                         "in highp vec4 a_position;\n"
                         "out highp vec4 tf_feedback;\n"
                         "void main()\n"
                         "{\n"
                         "    gl_Position = a_position;\n"
                         "    tf_feedback = vec4(1.0, 1.0, 1.0, 1.0);\n"
                         "}\n";

    return specializeShader(source, m_context.getRenderContext().getType());
}

std::string FeedbackRecordVariableSelectionCase::getFragmentSource(void)
{
    return specializeShader(s_whiteOutputFragmentShader, m_context.getRenderContext().getType());
}

std::string FeedbackRecordVariableSelectionCase::getTessellationControlSource(void)
{
    std::string source = "${VERSION_DECL}\n"
                         "${EXTENSION_TESSELATION_SHADER}"
                         "layout(vertices=3) out;\n"
                         "void main()\n"
                         "{\n"
                         "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
                         "    gl_TessLevelOuter[0] = 1.0;\n"
                         "    gl_TessLevelOuter[1] = 1.0;\n"
                         "    gl_TessLevelOuter[2] = 1.0;\n"
                         "    gl_TessLevelInner[0] = 1.0;\n"
                         "}\n";

    return specializeShader(source, m_context.getRenderContext().getType());
}

std::string FeedbackRecordVariableSelectionCase::getTessellationEvaluationSource(void)
{
    std::string source = "${VERSION_DECL}\n"
                         "${EXTENSION_TESSELATION_SHADER}"
                         "layout(triangles) in;\n"
                         "out highp vec4 tf_feedback;\n"
                         "void main()\n"
                         "{\n"
                         "    gl_Position = gl_in[0].gl_Position * gl_TessCoord.x + gl_in[1].gl_Position * "
                         "gl_TessCoord.y + gl_in[2].gl_Position * gl_TessCoord.z;\n"
                         "    tf_feedback = vec4(2.0, 2.0, 2.0, 2.0);\n"
                         "}\n";

    return specializeShader(source, m_context.getRenderContext().getType());
}

std::string FeedbackRecordVariableSelectionCase::getGeometrySource(void)
{
    std::string source =
        "${VERSION_DECL}\n"
        "${EXTENSION_GEOMETRY_SHADER}"
        "layout (triangles) in;\n"
        "layout (triangle_strip, max_vertices=3) out;\n"
        "out highp vec4 tf_feedback;\n"
        "void main()\n"
        "{\n"
        "    for (int ndx = 0; ndx < 3; ++ndx)\n"
        "    {\n"
        "        gl_Position = gl_in[ndx].gl_Position + vec4(float(ndx), float(ndx)*float(ndx), 0.0, 0.0);\n"
        "        tf_feedback = vec4(3.0, 3.0, 3.0, 3.0);\n"
        "        EmitVertex();\n"
        "    }\n"
        "    EndPrimitive();\n"
        "}\n";

    return specializeShader(source, m_context.getRenderContext().getType());
}

} // namespace

TessellationGeometryInteractionTests::TessellationGeometryInteractionTests(Context &context, bool isGL45)
    : TestCaseGroup(context, "tessellation_geometry_interaction", "Tessellation and geometry shader interaction tests")
    , m_isGL45(isGL45)
{
}

TessellationGeometryInteractionTests::~TessellationGeometryInteractionTests(void)
{
}

void TessellationGeometryInteractionTests::init(void)
{
    tcu::TestCaseGroup *const renderGroup    = new tcu::TestCaseGroup(m_testCtx, "render", "Various render tests");
    tcu::TestCaseGroup *const feedbackGroup  = new tcu::TestCaseGroup(m_testCtx, "feedback", "Test transform feedback");
    tcu::TestCaseGroup *const pointSizeGroup = new tcu::TestCaseGroup(m_testCtx, "point_size", "Test point size");

    addChild(renderGroup);
    addChild(feedbackGroup);
    addChild(pointSizeGroup);

    // .render
    {
        tcu::TestCaseGroup *const passthroughGroup = new tcu::TestCaseGroup(
            m_testCtx, "passthrough", "Render various types with either passthrough geometry or tessellation shader");
        tcu::TestCaseGroup *const limitGroup =
            new tcu::TestCaseGroup(m_testCtx, "limits", "Render with properties near their limits");
        tcu::TestCaseGroup *const scatterGroup =
            new tcu::TestCaseGroup(m_testCtx, "scatter", "Scatter output primitives");

        renderGroup->addChild(passthroughGroup);
        renderGroup->addChild(limitGroup);
        renderGroup->addChild(scatterGroup);

        // .passthrough
        {
            // tessellate_tris_passthrough_geometry_no_change
            // tessellate_quads_passthrough_geometry_no_change
            // tessellate_isolines_passthrough_geometry_no_change
            passthroughGroup->addChild(new IdentityGeometryShaderCase(
                m_context, "tessellate_tris_passthrough_geometry_no_change",
                "Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_TRIANGLES));
            passthroughGroup->addChild(new IdentityGeometryShaderCase(
                m_context, "tessellate_quads_passthrough_geometry_no_change",
                "Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_QUADS));
            passthroughGroup->addChild(new IdentityGeometryShaderCase(
                m_context, "tessellate_isolines_passthrough_geometry_no_change",
                "Passthrough geometry shader has no effect", IdentityGeometryShaderCase::CASE_ISOLINES));

            // passthrough_tessellation_geometry_shade_triangles_no_change
            // passthrough_tessellation_geometry_shade_lines_no_change
            passthroughGroup->addChild(new IdentityTessellationShaderCase(
                m_context, "passthrough_tessellation_geometry_shade_triangles_no_change",
                "Passthrough tessellation shader has no effect", IdentityTessellationShaderCase::CASE_TRIANGLES));
            passthroughGroup->addChild(new IdentityTessellationShaderCase(
                m_context, "passthrough_tessellation_geometry_shade_lines_no_change",
                "Passthrough tessellation shader has no effect", IdentityTessellationShaderCase::CASE_ISOLINES));
        }

        // .limits
        {
            static const struct LimitCaseDef
            {
                const char *name;
                const char *desc;
                int flags;
            } cases[] = {
                // Test single limit
                {"output_required_max_tessellation", "Minimum maximum tessellation level",
                 GridRenderCase::FLAG_TESSELLATION_MAX_SPEC},
                {"output_implementation_max_tessellation", "Maximum tessellation level supported by the implementation",
                 GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION},
                {"output_required_max_geometry", "Output minimum maximum number of vertices the geometry shader",
                 GridRenderCase::FLAG_GEOMETRY_MAX_SPEC},
                {"output_implementation_max_geometry",
                 "Output maximum number of vertices in the geometry shader supported by the implementation",
                 GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION},
                {"output_required_max_invocations", "Minimum maximum number of geometry shader invocations",
                 GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC},
                {"output_implementation_max_invocations",
                 "Maximum number of geometry shader invocations supported by the implementation",
                 GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION},
            };

            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ++ndx)
                limitGroup->addChild(new GridRenderCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].flags));
        }

        // .scatter
        {
            scatterGroup->addChild(new GridRenderCase(
                m_context, "geometry_scatter_instances",
                "Each geometry shader instance outputs its primitives far from other instances of the same execution",
                GridRenderCase::FLAG_GEOMETRY_SCATTER_INSTANCES));
            scatterGroup->addChild(new GridRenderCase(
                m_context, "geometry_scatter_primitives",
                "Each geometry shader instance outputs its primitives far from other primitives of the same instance",
                GridRenderCase::FLAG_GEOMETRY_SCATTER_PRIMITIVES | GridRenderCase::FLAG_GEOMETRY_SEPARATE_PRIMITIVES));
            scatterGroup->addChild(new GridRenderCase(
                m_context, "geometry_scatter_layers",
                "Each geometry shader instance outputs its primitives to multiple layers and far from other primitives "
                "of the same instance",
                GridRenderCase::FLAG_GEOMETRY_SCATTER_LAYERS | GridRenderCase::FLAG_GEOMETRY_SEPARATE_PRIMITIVES));
        }
    }

    // .feedback
    {
        static const struct PrimitiveCaseConfig
        {
            const char *name;
            const char *description;
            FeedbackPrimitiveTypeCase::TessellationOutputType tessellationOutput;
            FeedbackPrimitiveTypeCase::TessellationPointMode tessellationPointMode;
            FeedbackPrimitiveTypeCase::GeometryOutputType geometryOutputType;
        } caseConfigs[] = {
            // tess output triangles -> geo input triangles, output points
            {"tessellation_output_triangles_geometry_output_points",
             "Tessellation outputs triangles, geometry outputs points",
             FeedbackPrimitiveTypeCase::TESSELLATION_OUT_TRIANGLES,
             FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF, FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS},

            // tess output quads <-> geo input triangles, output points
            {"tessellation_output_quads_geometry_output_points", "Tessellation outputs quads, geometry outputs points",
             FeedbackPrimitiveTypeCase::TESSELLATION_OUT_QUADS, FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF,
             FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS},

            // tess output isolines <-> geo input lines, output points
            {"tessellation_output_isolines_geometry_output_points",
             "Tessellation outputs isolines, geometry outputs points",
             FeedbackPrimitiveTypeCase::TESSELLATION_OUT_ISOLINES,
             FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_OFF, FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_POINTS},

            // tess output triangles, point_mode <-> geo input points, output lines
            {"tessellation_output_triangles_point_mode_geometry_output_lines",
             "Tessellation outputs triangles in point mode, geometry outputs lines",
             FeedbackPrimitiveTypeCase::TESSELLATION_OUT_TRIANGLES,
             FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON, FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_LINES},

            // tess output quads, point_mode <-> geo input points, output lines
            {"tessellation_output_quads_point_mode_geometry_output_lines",
             "Tessellation outputs quads in point mode, geometry outputs lines",
             FeedbackPrimitiveTypeCase::TESSELLATION_OUT_QUADS, FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON,
             FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_LINES},

            // tess output isolines, point_mode <-> geo input points, output triangles
            {"tessellation_output_isolines_point_mode_geometry_output_triangles",
             "Tessellation outputs isolines in point mode, geometry outputs triangles",
             FeedbackPrimitiveTypeCase::TESSELLATION_OUT_ISOLINES, FeedbackPrimitiveTypeCase::TESSELLATION_POINTMODE_ON,
             FeedbackPrimitiveTypeCase::GEOMETRY_OUTPUT_TRIANGLES},
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(caseConfigs); ++ndx)
        {
            feedbackGroup->addChild(new FeedbackPrimitiveTypeCase(
                m_context, caseConfigs[ndx].name, caseConfigs[ndx].description, caseConfigs[ndx].tessellationOutput,
                caseConfigs[ndx].tessellationPointMode, caseConfigs[ndx].geometryOutputType));
        }

        feedbackGroup->addChild(new FeedbackRecordVariableSelectionCase(
            m_context, "record_variable_selection",
            "Record a variable that has been declared as an output variable in multiple shader stages"));
    }

    // .point_size
    {
        static const struct PointSizeCaseConfig
        {
            const int caseMask;
            const bool isSupportedInGL; // is this case supported in OpenGL
        } caseConfigs[] = {
            {PointSizeCase::FLAG_VERTEX_SET, true},
            {PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET, true},
            {PointSizeCase::FLAG_GEOMETRY_SET, true},
            {PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_CONTROL_SET, false},
            {PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET, true},
            {PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_DONT_SET, false},
            {PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_GEOMETRY_SET, true},
            {PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET |
                 PointSizeCase::FLAG_GEOMETRY_SET,
             true},
            {PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_ADD | PointSizeCase::FLAG_GEOMETRY_ADD,
             true},
            {PointSizeCase::FLAG_VERTEX_SET | PointSizeCase::FLAG_TESSELLATION_EVALUATION_SET |
                 PointSizeCase::FLAG_GEOMETRY_DONT_SET,
             false},
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(caseConfigs); ++ndx)
        {
            if (m_isGL45 && !caseConfigs[ndx].isSupportedInGL)
                continue;

            const std::string name = PointSizeCase::genTestCaseName(caseConfigs[ndx].caseMask);
            const std::string desc = PointSizeCase::genTestCaseDescription(caseConfigs[ndx].caseMask);

            pointSizeGroup->addChild(
                new PointSizeCase(m_context, name.c_str(), desc.c_str(), caseConfigs[ndx].caseMask));
        }
    }
}

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