/*-------------------------------------------------------------------------
 * 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 stress tests.
 *//*--------------------------------------------------------------------*/

#include "es31sTessellationGeometryInteractionTests.hpp"

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

#include <sstream>

namespace deqp
{
namespace gles31
{
namespace Stress
{
namespace
{

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,
    };

    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);

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

    enum
    {
        RENDER_SIZE = 256
    };

    std::string m_description;

    const int m_flags;

    glu::ShaderProgram *m_program;
    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(nullptr)
    , 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));
}

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

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

    // Requirements

    if (!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 tessellation and geometry shaders that output a large number of primitives.\n"
                       << m_description << tcu::TestLog::EndMessage;

    // 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 int numComponentsPerVertex = 8; // vec4 pos, vec4 color

                // 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);

                const int numVerticesPerInvocation   = numSliceNodes * 2;
                const int numPrimitivesPerInvocation = (numSliceNodes - 1) * 2;

                const int geometryVerticesPerPrimitive      = numVerticesPerInvocation * geometryShaderInvocations;
                const int 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));

                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 = nullptr;
}

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());

    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);

    // clear viewport
    gl.clear(GL_COLOR_BUFFER_BIT);

    // draw
    {
        glw::GLenum glerror;

        gl.drawArrays(GL_PATCHES, 0, 1);

        // allow always OOM
        glerror = gl.getError();
        if (glerror == GL_OUT_OF_MEMORY)
            throw AllowedRenderFailureException("got GL_OUT_OF_MEMORY while drawing");

        GLU_EXPECT_NO_ERROR(glerror, "draw patches");
    }

    // Read layers

    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;
    }
}

const char *GridRenderCase::getVertexSource(void)
{
    return "#version 310 es\n"
           "in highp vec4 a_position;\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n"
           "}\n";
}

const char *GridRenderCase::getFragmentSource(void)
{
    return "#version 310 es\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";
}

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

    buf << "#version 310 es\n"
           "#extension GL_EXT_tessellation_shader : require\n"
           "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 buf.str();
}

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

    buf << "#version 310 es\n"
           "#extension GL_EXT_tessellation_shader : require\n"
           "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"
           "    // 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"
           "    // Calculate position in tessellation grid\n"
           "    v_tessellationGridPosition = ivec2(round(gl_TessCoord.xy * float("
        << tessLevel
        << ")));\n"
           "}\n";

    return buf.str();
}

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

    buf << "#version 310 es\n"
           "#extension GL_EXT_geometry_shader : require\n"
           "layout(triangles, invocations="
        << numInstances
        << ") in;\n"
           "layout(triangle_strip, max_vertices="
        << (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"
           "    // 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"
           "\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"
           "}\n";

    return buf.str();
}

} // namespace

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

TessellationGeometryInteractionTests::~TessellationGeometryInteractionTests(void)
{
}

void TessellationGeometryInteractionTests::init(void)
{
    tcu::TestCaseGroup *const multilimitGroup =
        new tcu::TestCaseGroup(m_testCtx, "render_multiple_limits", "Various render tests");

    addChild(multilimitGroup);

    // .render_multiple_limits
    {
        static const struct LimitCaseDef
        {
            const char *name;
            const char *desc;
            int flags;
        } cases[] = {
            // Test multiple limits at the same time

            {"output_required_max_tessellation_max_geometry",
             "Minimum maximum tessellation level and geometry shader output vertices",
             GridRenderCase::FLAG_TESSELLATION_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_MAX_SPEC},
            {"output_implementation_max_tessellation_max_geometry",
             "Maximum tessellation level and geometry shader output vertices supported by the implementation",
             GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION | GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION},
            {"output_required_max_tessellation_max_invocations",
             "Minimum maximum tessellation level and geometry shader invocations",
             GridRenderCase::FLAG_TESSELLATION_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC},
            {"output_implementation_max_tessellation_max_invocations",
             "Maximum tessellation level and geometry shader invocations supported by the implementation",
             GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION |
                 GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION},
            {"output_required_max_geometry_max_invocations",
             "Minimum maximum geometry shader output vertices and invocations",
             GridRenderCase::FLAG_GEOMETRY_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC},
            {"output_implementation_max_geometry_max_invocations",
             "Maximum geometry shader output vertices and invocations invocations supported by the implementation",
             GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION |
                 GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION},

            // Test all limits simultaneously
            {"output_max_required", "Output minimum maximum number of vertices",
             GridRenderCase::FLAG_TESSELLATION_MAX_SPEC | GridRenderCase::FLAG_GEOMETRY_MAX_SPEC |
                 GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_SPEC},
            {"output_max_implementation", "Output maximum number of vertices supported by the implementation",
             GridRenderCase::FLAG_TESSELLATION_MAX_IMPLEMENTATION | GridRenderCase::FLAG_GEOMETRY_MAX_IMPLEMENTATION |
                 GridRenderCase::FLAG_GEOMETRY_INVOCATIONS_MAX_IMPLEMENTATION},
        };

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

} // namespace Stress
} // namespace gles31
} // namespace deqp
