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

#include "es31fDrawTests.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deMemory.h"
#include "tcuRenderTarget.hpp"
#include "tcuVectorUtil.hpp"
#include "sglrGLContext.hpp"
#include "glsDrawTest.hpp"
#include "gluStrUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluCallLogWrapper.hpp"

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

#include <set>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

enum TestIterationType
{
    TYPE_DRAW_COUNT,     // !< test with 1, 5, and 25 primitives
    TYPE_INSTANCE_COUNT, // !< test with 1, 4, and 11 instances

    TYPE_LAST
};

static const char *s_commonVertexShaderSource   = "#version 310 es\n"
                                                  "in highp vec4 a_position;\n"
                                                  "void main (void)\n"
                                                  "{\n"
                                                  "    gl_Position = a_position;\n"
                                                  "}\n";
static const char *s_commonFragmentShaderSource = "#version 310 es\n"
                                                  "layout(location = 0) out highp vec4 fragColor;\n"
                                                  "void main (void)\n"
                                                  "{\n"
                                                  "    fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
                                                  "}\n";

static const char *s_colorVertexShaderSource   = "#version 310 es\n"
                                                 "in highp vec4 a_position;\n"
                                                 "in highp vec4 a_color;\n"
                                                 "out highp vec4 v_color;\n"
                                                 "void main (void)\n"
                                                 "{\n"
                                                 "    gl_Position = a_position;\n"
                                                 "    v_color = a_color;\n"
                                                 "}\n";
static const char *s_colorFragmentShaderSource = "#version 310 es\n"
                                                 "layout(location = 0) out highp vec4 fragColor;\n"
                                                 "in highp vec4 v_color;\n"
                                                 "void main (void)\n"
                                                 "{\n"
                                                 "    fragColor = v_color;\n"
                                                 "}\n";
struct DrawElementsCommand
{
    uint32_t count;
    uint32_t primCount;
    uint32_t firstIndex;
    int32_t baseVertex;
    uint32_t reservedMustBeZero;
};
DE_STATIC_ASSERT(5 * sizeof(uint32_t) == sizeof(DrawElementsCommand)); // tight packing

struct DrawArraysCommand
{
    uint32_t count;
    uint32_t primCount;
    uint32_t first;
    uint32_t reservedMustBeZero;
};
DE_STATIC_ASSERT(4 * sizeof(uint32_t) == sizeof(DrawArraysCommand)); // tight packing

// Verifies image contains only yellow or greeen, or a linear combination
// of these colors.
static bool verifyImageYellowGreen(const tcu::Surface &image, tcu::TestLog &log)
{
    using tcu::TestLog;

    const int colorThreshold = 20;

    tcu::Surface error(image.getWidth(), image.getHeight());
    bool isOk = true;

    for (int y = 0; y < image.getHeight(); y++)
        for (int x = 0; x < image.getWidth(); x++)
        {
            const tcu::RGBA pixel = image.getPixel(x, y);
            bool pixelOk          = true;

            // Any pixel with !(G ~= 255) is faulty (not a linear combinations of green and yellow)
            if (de::abs(pixel.getGreen() - 255) > colorThreshold)
                pixelOk = false;

            // Any pixel with !(B ~= 0) is faulty (not a linear combinations of green and yellow)
            if (de::abs(pixel.getBlue() - 0) > colorThreshold)
                pixelOk = false;

            error.setPixel(x, y, (pixelOk) ? (tcu::RGBA(0, 255, 0, 255)) : (tcu::RGBA(255, 0, 0, 255)));
            isOk = isOk && pixelOk;
        }

    if (!isOk)
    {
        log << TestLog::Message << "Image verification failed." << TestLog::EndMessage;
        log << TestLog::ImageSet("Verfication result", "Result of rendering")
            << TestLog::Image("Result", "Result", image) << TestLog::Image("ErrorMask", "Error mask", error)
            << TestLog::EndImageSet;
    }
    else
    {
        log << TestLog::ImageSet("Verfication result", "Result of rendering")
            << TestLog::Image("Result", "Result", image) << TestLog::EndImageSet;
    }

    return isOk;
}

static void addTestIterations(gls::DrawTest *test, gls::DrawTestSpec &spec, TestIterationType type)
{
    if (type == TYPE_DRAW_COUNT)
    {
        spec.primitiveCount = 1;
        test->addIteration(spec, "draw count = 1");

        spec.primitiveCount = 5;
        test->addIteration(spec, "draw count = 5");

        spec.primitiveCount = 25;
        test->addIteration(spec, "draw count = 25");
    }
    else if (type == TYPE_INSTANCE_COUNT)
    {
        spec.instanceCount = 1;
        test->addIteration(spec, "instance count = 1");

        spec.instanceCount = 4;
        test->addIteration(spec, "instance count = 4");

        spec.instanceCount = 11;
        test->addIteration(spec, "instance count = 11");
    }
    else
        DE_ASSERT(false);
}

static void genBasicSpec(gls::DrawTestSpec &spec, glu::ContextType contextType, gls::DrawTestSpec::DrawMethod method)
{
    spec.apiType            = glu::isContextTypeES(contextType) ? glu::ApiType::es(3, 1) : contextType.getAPI();
    spec.primitive          = gls::DrawTestSpec::PRIMITIVE_TRIANGLES;
    spec.primitiveCount     = 5;
    spec.drawMethod         = method;
    spec.indexType          = gls::DrawTestSpec::INDEXTYPE_LAST;
    spec.indexPointerOffset = 0;
    spec.indexStorage       = gls::DrawTestSpec::STORAGE_LAST;
    spec.first              = 0;
    spec.indexMin           = 0;
    spec.indexMax           = 0;
    spec.instanceCount      = 1;
    spec.indirectOffset     = 0;

    spec.attribs.resize(2);

    spec.attribs[0].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
    spec.attribs[0].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
    spec.attribs[0].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
    spec.attribs[0].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
    spec.attribs[0].componentCount      = 4;
    spec.attribs[0].offset              = 0;
    spec.attribs[0].stride              = 0;
    spec.attribs[0].normalize           = false;
    spec.attribs[0].instanceDivisor     = 0;
    spec.attribs[0].useDefaultAttribute = false;

    spec.attribs[1].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
    spec.attribs[1].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
    spec.attribs[1].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
    spec.attribs[1].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
    spec.attribs[1].componentCount      = 2;
    spec.attribs[1].offset              = 0;
    spec.attribs[1].stride              = 0;
    spec.attribs[1].normalize           = false;
    spec.attribs[1].instanceDivisor     = 0;
    spec.attribs[1].useDefaultAttribute = false;
}

static std::string sizeToString(int size)
{
    if (size < 1024)
        return de::toString(size) + " byte(s)";
    if (size < 1024 * 1024)
        return de::toString(size / 1024) + " KB";
    return de::toString(size / 1024 / 1024) + " MB";
}

class AttributeGroup : public TestCaseGroup
{
public:
    AttributeGroup(Context &context, const char *name, const char *descr, gls::DrawTestSpec::DrawMethod drawMethod,
                   gls::DrawTestSpec::Primitive primitive, gls::DrawTestSpec::IndexType indexType,
                   gls::DrawTestSpec::Storage indexStorage);
    ~AttributeGroup(void);

    void init(void);

private:
    gls::DrawTestSpec::DrawMethod m_method;
    gls::DrawTestSpec::Primitive m_primitive;
    gls::DrawTestSpec::IndexType m_indexType;
    gls::DrawTestSpec::Storage m_indexStorage;
};

AttributeGroup::AttributeGroup(Context &context, const char *name, const char *descr,
                               gls::DrawTestSpec::DrawMethod drawMethod, gls::DrawTestSpec::Primitive primitive,
                               gls::DrawTestSpec::IndexType indexType, gls::DrawTestSpec::Storage indexStorage)
    : TestCaseGroup(context, name, descr)
    , m_method(drawMethod)
    , m_primitive(primitive)
    , m_indexType(indexType)
    , m_indexStorage(indexStorage)
{
}

AttributeGroup::~AttributeGroup(void)
{
}

void AttributeGroup::init(void)
{
    // Single attribute
    {
        gls::DrawTest *test =
            new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "single_attribute", "Single attribute array.");
        glu::ContextType contextType = m_context.getRenderContext().getType();
        gls::DrawTestSpec spec;

        spec.apiType            = glu::isContextTypeES(contextType) ? glu::ApiType::es(3, 1) : contextType.getAPI();
        spec.primitive          = m_primitive;
        spec.primitiveCount     = 5;
        spec.drawMethod         = m_method;
        spec.indexType          = m_indexType;
        spec.indexPointerOffset = 0;
        spec.indexStorage       = m_indexStorage;
        spec.first              = 0;
        spec.indexMin           = 0;
        spec.indexMax           = 0;
        spec.instanceCount      = 1;
        spec.indirectOffset     = 0;

        spec.attribs.resize(1);

        spec.attribs[0].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
        spec.attribs[0].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
        spec.attribs[0].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.attribs[0].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
        spec.attribs[0].componentCount      = 2;
        spec.attribs[0].offset              = 0;
        spec.attribs[0].stride              = 0;
        spec.attribs[0].normalize           = false;
        spec.attribs[0].instanceDivisor     = 0;
        spec.attribs[0].useDefaultAttribute = false;

        addTestIterations(test, spec, TYPE_DRAW_COUNT);

        this->addChild(test);
    }

    // Multiple attribute
    {
        gls::DrawTest *test          = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "multiple_attributes",
                                                         "Multiple attribute arrays.");
        glu::ContextType contextType = m_context.getRenderContext().getType();
        gls::DrawTestSpec spec;

        spec.apiType            = glu::isContextTypeES(contextType) ? glu::ApiType::es(3, 1) : contextType.getAPI();
        spec.primitive          = m_primitive;
        spec.primitiveCount     = 5;
        spec.drawMethod         = m_method;
        spec.indexType          = m_indexType;
        spec.indexPointerOffset = 0;
        spec.indexStorage       = m_indexStorage;
        spec.first              = 0;
        spec.indexMin           = 0;
        spec.indexMax           = 0;
        spec.instanceCount      = 1;
        spec.indirectOffset     = 0;

        spec.attribs.resize(2);

        spec.attribs[0].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
        spec.attribs[0].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
        spec.attribs[0].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.attribs[0].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
        spec.attribs[0].componentCount      = 4;
        spec.attribs[0].offset              = 0;
        spec.attribs[0].stride              = 0;
        spec.attribs[0].normalize           = false;
        spec.attribs[0].instanceDivisor     = 0;
        spec.attribs[0].useDefaultAttribute = false;

        spec.attribs[1].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
        spec.attribs[1].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
        spec.attribs[1].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.attribs[1].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
        spec.attribs[1].componentCount      = 2;
        spec.attribs[1].offset              = 0;
        spec.attribs[1].stride              = 0;
        spec.attribs[1].normalize           = false;
        spec.attribs[1].instanceDivisor     = 0;
        spec.attribs[1].useDefaultAttribute = false;

        addTestIterations(test, spec, TYPE_DRAW_COUNT);

        this->addChild(test);
    }

    // Multiple attribute, second one divided
    {
        gls::DrawTest *test = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "instanced_attributes",
                                                "Instanced attribute array.");
        glu::ContextType contextType = m_context.getRenderContext().getType();
        gls::DrawTestSpec spec;

        spec.apiType            = glu::isContextTypeES(contextType) ? glu::ApiType::es(3, 1) : contextType.getAPI();
        spec.primitive          = m_primitive;
        spec.primitiveCount     = 5;
        spec.drawMethod         = m_method;
        spec.indexType          = m_indexType;
        spec.indexPointerOffset = 0;
        spec.indexStorage       = m_indexStorage;
        spec.first              = 0;
        spec.indexMin           = 0;
        spec.indexMax           = 0;
        spec.instanceCount      = 1;
        spec.indirectOffset     = 0;

        spec.attribs.resize(3);

        spec.attribs[0].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
        spec.attribs[0].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
        spec.attribs[0].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.attribs[0].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
        spec.attribs[0].componentCount      = 4;
        spec.attribs[0].offset              = 0;
        spec.attribs[0].stride              = 0;
        spec.attribs[0].normalize           = false;
        spec.attribs[0].instanceDivisor     = 0;
        spec.attribs[0].useDefaultAttribute = false;

        // Add another position component so the instances wont be drawn on each other
        spec.attribs[1].inputType                   = gls::DrawTestSpec::INPUTTYPE_FLOAT;
        spec.attribs[1].outputType                  = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
        spec.attribs[1].storage                     = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.attribs[1].usage                       = gls::DrawTestSpec::USAGE_STATIC_DRAW;
        spec.attribs[1].componentCount              = 2;
        spec.attribs[1].offset                      = 0;
        spec.attribs[1].stride                      = 0;
        spec.attribs[1].normalize                   = false;
        spec.attribs[1].instanceDivisor             = 1;
        spec.attribs[1].useDefaultAttribute         = false;
        spec.attribs[1].additionalPositionAttribute = true;

        // Instanced color
        spec.attribs[2].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
        spec.attribs[2].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
        spec.attribs[2].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.attribs[2].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
        spec.attribs[2].componentCount      = 3;
        spec.attribs[2].offset              = 0;
        spec.attribs[2].stride              = 0;
        spec.attribs[2].normalize           = false;
        spec.attribs[2].instanceDivisor     = 1;
        spec.attribs[2].useDefaultAttribute = false;

        addTestIterations(test, spec, TYPE_INSTANCE_COUNT);

        this->addChild(test);
    }

    // Multiple attribute, second one default
    {
        gls::DrawTest *test          = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), "default_attribute",
                                                         "Attribute specified with glVertexAttrib*.");
        glu::ContextType contextType = m_context.getRenderContext().getType();
        gls::DrawTestSpec spec;

        spec.apiType            = glu::isContextTypeES(contextType) ? glu::ApiType::es(3, 1) : contextType.getAPI();
        spec.primitive          = m_primitive;
        spec.primitiveCount     = 5;
        spec.drawMethod         = m_method;
        spec.indexType          = m_indexType;
        spec.indexPointerOffset = 0;
        spec.indexStorage       = m_indexStorage;
        spec.first              = 0;
        spec.indexMin           = 0;
        spec.indexMax           = 0;
        spec.instanceCount      = 1;
        spec.indirectOffset     = 0;

        spec.attribs.resize(2);

        spec.attribs[0].inputType           = gls::DrawTestSpec::INPUTTYPE_FLOAT;
        spec.attribs[0].outputType          = gls::DrawTestSpec::OUTPUTTYPE_VEC2;
        spec.attribs[0].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.attribs[0].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
        spec.attribs[0].componentCount      = 2;
        spec.attribs[0].offset              = 0;
        spec.attribs[0].stride              = 0;
        spec.attribs[0].normalize           = false;
        spec.attribs[0].instanceDivisor     = 0;
        spec.attribs[0].useDefaultAttribute = false;

        struct IOPair
        {
            gls::DrawTestSpec::InputType input;
            gls::DrawTestSpec::OutputType output;
            int componentCount;
        } iopairs[] = {
            {gls::DrawTestSpec::INPUTTYPE_FLOAT, gls::DrawTestSpec::OUTPUTTYPE_VEC2, 4},
            {gls::DrawTestSpec::INPUTTYPE_FLOAT, gls::DrawTestSpec::OUTPUTTYPE_VEC4, 2},
            {gls::DrawTestSpec::INPUTTYPE_INT, gls::DrawTestSpec::OUTPUTTYPE_IVEC3, 4},
            {gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT, gls::DrawTestSpec::OUTPUTTYPE_UVEC2, 4},
        };

        for (int ioNdx = 0; ioNdx < DE_LENGTH_OF_ARRAY(iopairs); ++ioNdx)
        {
            const std::string desc = gls::DrawTestSpec::inputTypeToString(iopairs[ioNdx].input) +
                                     de::toString(iopairs[ioNdx].componentCount) + " to " +
                                     gls::DrawTestSpec::outputTypeToString(iopairs[ioNdx].output);

            spec.attribs[1].inputType           = iopairs[ioNdx].input;
            spec.attribs[1].outputType          = iopairs[ioNdx].output;
            spec.attribs[1].storage             = gls::DrawTestSpec::STORAGE_BUFFER;
            spec.attribs[1].usage               = gls::DrawTestSpec::USAGE_STATIC_DRAW;
            spec.attribs[1].componentCount      = iopairs[ioNdx].componentCount;
            spec.attribs[1].offset              = 0;
            spec.attribs[1].stride              = 0;
            spec.attribs[1].normalize           = false;
            spec.attribs[1].instanceDivisor     = 0;
            spec.attribs[1].useDefaultAttribute = true;

            test->addIteration(spec, desc.c_str());
        }

        this->addChild(test);
    }
}

class IndexGroup : public TestCaseGroup
{
public:
    IndexGroup(Context &context, const char *name, const char *descr, gls::DrawTestSpec::DrawMethod drawMethod);
    ~IndexGroup(void);

    void init(void);

private:
    gls::DrawTestSpec::DrawMethod m_method;
};

IndexGroup::IndexGroup(Context &context, const char *name, const char *descr, gls::DrawTestSpec::DrawMethod drawMethod)
    : TestCaseGroup(context, name, descr)
    , m_method(drawMethod)
{
}

IndexGroup::~IndexGroup(void)
{
}

void IndexGroup::init(void)
{
    struct IndexTest
    {
        gls::DrawTestSpec::IndexType type;
        int offsets[3];
    };

    const IndexTest tests[] = {
        {gls::DrawTestSpec::INDEXTYPE_BYTE, {0, 1, -1}},
        {gls::DrawTestSpec::INDEXTYPE_SHORT, {0, 2, -1}},
        {gls::DrawTestSpec::INDEXTYPE_INT, {0, 4, -1}},
    };

    gls::DrawTestSpec spec;
    genBasicSpec(spec, m_context.getRenderContext().getType(), m_method);

    spec.indexStorage = gls::DrawTestSpec::STORAGE_BUFFER;

    for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
    {
        const IndexTest &indexTest = tests[testNdx];

        const std::string name = std::string("index_") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
        const std::string desc = std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
        gls::DrawTest *test    = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());

        spec.indexType = indexTest.type;

        for (int iterationNdx = 0;
             iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.offsets) && indexTest.offsets[iterationNdx] != -1;
             ++iterationNdx)
        {
            const std::string iterationDesc =
                std::string("first vertex ") +
                de::toString(indexTest.offsets[iterationNdx] / gls::DrawTestSpec::indexTypeSize(indexTest.type));
            spec.indexPointerOffset = indexTest.offsets[iterationNdx];
            test->addIteration(spec, iterationDesc.c_str());
        }

        addChild(test);
    }
}

class BaseVertexGroup : public TestCaseGroup
{
public:
    BaseVertexGroup(Context &context, const char *name, const char *descr, gls::DrawTestSpec::DrawMethod drawMethod);
    ~BaseVertexGroup(void);

    void init(void);

private:
    gls::DrawTestSpec::DrawMethod m_method;
};

BaseVertexGroup::BaseVertexGroup(Context &context, const char *name, const char *descr,
                                 gls::DrawTestSpec::DrawMethod drawMethod)
    : TestCaseGroup(context, name, descr)
    , m_method(drawMethod)
{
}

BaseVertexGroup::~BaseVertexGroup(void)
{
}

void BaseVertexGroup::init(void)
{
    struct IndexTest
    {
        bool positiveBase;
        gls::DrawTestSpec::IndexType type;
        int baseVertex[2];
    };

    const IndexTest tests[] = {
        {true, gls::DrawTestSpec::INDEXTYPE_BYTE, {1, 2}},     {true, gls::DrawTestSpec::INDEXTYPE_SHORT, {1, 2}},
        {true, gls::DrawTestSpec::INDEXTYPE_INT, {1, 2}},      {false, gls::DrawTestSpec::INDEXTYPE_BYTE, {-1, -2}},
        {false, gls::DrawTestSpec::INDEXTYPE_SHORT, {-1, -2}}, {false, gls::DrawTestSpec::INDEXTYPE_INT, {-1, -2}},
    };

    gls::DrawTestSpec spec;
    genBasicSpec(spec, m_context.getRenderContext().getType(), m_method);

    spec.indexStorage = gls::DrawTestSpec::STORAGE_BUFFER;

    for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(tests); ++testNdx)
    {
        const IndexTest &indexTest = tests[testNdx];

        const std::string name = std::string("index_") + (indexTest.positiveBase ? "" : "neg_") +
                                 gls::DrawTestSpec::indexTypeToString(indexTest.type);
        const std::string desc = std::string("index ") + gls::DrawTestSpec::indexTypeToString(indexTest.type);
        gls::DrawTest *test    = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());

        spec.indexType = indexTest.type;

        for (int iterationNdx = 0; iterationNdx < DE_LENGTH_OF_ARRAY(indexTest.baseVertex); ++iterationNdx)
        {
            const std::string iterationDesc =
                std::string("base vertex ") + de::toString(indexTest.baseVertex[iterationNdx]);
            spec.baseVertex = indexTest.baseVertex[iterationNdx];
            test->addIteration(spec, iterationDesc.c_str());
        }

        addChild(test);
    }
}

class FirstGroup : public TestCaseGroup
{
public:
    FirstGroup(Context &context, const char *name, const char *descr, gls::DrawTestSpec::DrawMethod drawMethod);
    ~FirstGroup(void);

    void init(void);

private:
    gls::DrawTestSpec::DrawMethod m_method;
};

FirstGroup::FirstGroup(Context &context, const char *name, const char *descr, gls::DrawTestSpec::DrawMethod drawMethod)
    : TestCaseGroup(context, name, descr)
    , m_method(drawMethod)
{
}

FirstGroup::~FirstGroup(void)
{
}

void FirstGroup::init(void)
{
    const int firsts[] = {1, 3, 17};

    gls::DrawTestSpec spec;
    genBasicSpec(spec, m_context.getRenderContext().getType(), m_method);

    for (int firstNdx = 0; firstNdx < DE_LENGTH_OF_ARRAY(firsts); ++firstNdx)
    {
        const std::string name = std::string("first_") + de::toString(firsts[firstNdx]);
        const std::string desc = std::string("first ") + de::toString(firsts[firstNdx]);
        gls::DrawTest *test    = new gls::DrawTest(m_testCtx, m_context.getRenderContext(), name.c_str(), desc.c_str());

        spec.first = firsts[firstNdx];

        addTestIterations(test, spec, TYPE_DRAW_COUNT);

        this->addChild(test);
    }
}

class MethodGroup : public TestCaseGroup
{
public:
    MethodGroup(Context &context, const char *name, const char *descr, gls::DrawTestSpec::DrawMethod drawMethod);
    ~MethodGroup(void);

    void init(void);

private:
    gls::DrawTestSpec::DrawMethod m_method;
};

MethodGroup::MethodGroup(Context &context, const char *name, const char *descr,
                         gls::DrawTestSpec::DrawMethod drawMethod)
    : TestCaseGroup(context, name, descr)
    , m_method(drawMethod)
{
}

MethodGroup::~MethodGroup(void)
{
}

void MethodGroup::init(void)
{
    const bool indexed  = (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT);
    const bool hasFirst = (m_method == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT);

    const gls::DrawTestSpec::Primitive primitive[] = {
        gls::DrawTestSpec::PRIMITIVE_POINTS,       gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
        gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN, gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
        gls::DrawTestSpec::PRIMITIVE_LINES,        gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
        gls::DrawTestSpec::PRIMITIVE_LINE_LOOP};

    if (hasFirst)
    {
        // First-tests
        this->addChild(new FirstGroup(m_context, "first", "First tests", m_method));
    }

    if (indexed)
    {
        // Index-tests
        this->addChild(new IndexGroup(m_context, "indices", "Index tests", m_method));
        this->addChild(new BaseVertexGroup(m_context, "base_vertex", "Base vertex tests", m_method));
    }

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(primitive); ++ndx)
    {
        const std::string name = gls::DrawTestSpec::primitiveToString(primitive[ndx]);
        const std::string desc = gls::DrawTestSpec::primitiveToString(primitive[ndx]);

        this->addChild(new AttributeGroup(m_context, name.c_str(), desc.c_str(), m_method, primitive[ndx],
                                          gls::DrawTestSpec::INDEXTYPE_SHORT, gls::DrawTestSpec::STORAGE_BUFFER));
    }
}

class GridProgram : public sglr::ShaderProgram
{
public:
    GridProgram(void);

    void shadeVertices(const rr::VertexAttrib *inputs, rr::VertexPacket *const *packets, const int numPackets) const;
    void shadeFragments(rr::FragmentPacket *packets, const int numPackets,
                        const rr::FragmentShadingContext &context) const;
};

GridProgram::GridProgram(void)
    : sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
                          << sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
                          << sglr::pdec::VertexAttribute("a_offset", rr::GENERICVECTYPE_FLOAT)
                          << sglr::pdec::VertexAttribute("a_color", rr::GENERICVECTYPE_FLOAT)
                          << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
                          << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
                          << sglr::pdec::VertexSource("#version 310 es\n"
                                                      "in highp vec4 a_position;\n"
                                                      "in highp vec4 a_offset;\n"
                                                      "in highp vec4 a_color;\n"
                                                      "out highp vec4 v_color;\n"
                                                      "void main(void)\n"
                                                      "{\n"
                                                      "    gl_Position = a_position + a_offset;\n"
                                                      "    v_color = a_color;\n"
                                                      "}\n")
                          << sglr::pdec::FragmentSource("#version 310 es\n"
                                                        "layout(location = 0) out highp vec4 dEQP_FragColor;\n"
                                                        "in highp vec4 v_color;\n"
                                                        "void main(void)\n"
                                                        "{\n"
                                                        "    dEQP_FragColor = v_color;\n"
                                                        "}\n"))
{
}

void GridProgram::shadeVertices(const rr::VertexAttrib *inputs, rr::VertexPacket *const *packets,
                                const int numPackets) const
{
    for (int ndx = 0; ndx < numPackets; ++ndx)
    {
        packets[ndx]->position =
            rr::readVertexAttribFloat(inputs[0], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx) +
            rr::readVertexAttribFloat(inputs[1], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
        packets[ndx]->outputs[0] =
            rr::readVertexAttribFloat(inputs[2], packets[ndx]->instanceNdx, packets[ndx]->vertexNdx);
    }
}

void GridProgram::shadeFragments(rr::FragmentPacket *packets, const int numPackets,
                                 const rr::FragmentShadingContext &context) const
{
    for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
        for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
            rr::writeFragmentOutput(context, packetNdx, fragNdx, 0,
                                    rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx));
}

class InstancedGridRenderTest : public TestCase
{
public:
    InstancedGridRenderTest(Context &context, const char *name, const char *desc, int gridSide, bool useIndices);
    ~InstancedGridRenderTest(void);

    IterateResult iterate(void);

private:
    void renderTo(sglr::Context &ctx, sglr::ShaderProgram &program, tcu::Surface &dst);

    const int m_gridSide;
    const bool m_useIndices;
};

InstancedGridRenderTest::InstancedGridRenderTest(Context &context, const char *name, const char *desc, int gridSide,
                                                 bool useIndices)
    : TestCase(context, name, desc)
    , m_gridSide(gridSide)
    , m_useIndices(useIndices)
{
}

InstancedGridRenderTest::~InstancedGridRenderTest(void)
{
}

InstancedGridRenderTest::IterateResult InstancedGridRenderTest::iterate(void)
{
    const int renderTargetWidth  = de::min(1024, m_context.getRenderTarget().getWidth());
    const int renderTargetHeight = de::min(1024, m_context.getRenderTarget().getHeight());

    sglr::GLContext ctx(m_context.getRenderContext(), m_testCtx.getLog(),
                        sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS,
                        tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight));
    tcu::Surface surface(renderTargetWidth, renderTargetHeight);
    GridProgram program;

    // render

    renderTo(ctx, program, surface);

    // verify image
    // \note the green/yellow pattern is only for clarity. The test will only verify that all instances were drawn by looking for anything non-green/yellow.
    if (verifyImageYellowGreen(surface, m_testCtx.getLog()))
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result image invalid");
    return STOP;
}

void InstancedGridRenderTest::renderTo(sglr::Context &ctx, sglr::ShaderProgram &program, tcu::Surface &dst)
{
    const tcu::Vec4 green(0, 1, 0, 1);
    const tcu::Vec4 yellow(1, 1, 0, 1);

    uint32_t vaoID           = 0;
    uint32_t positionBuf     = 0;
    uint32_t offsetBuf       = 0;
    uint32_t colorBuf        = 0;
    uint32_t indexBuf        = 0;
    uint32_t drawIndirectBuf = 0;
    uint32_t programID       = ctx.createProgram(&program);
    int32_t posLocation      = ctx.getAttribLocation(programID, "a_position");
    int32_t offsetLocation   = ctx.getAttribLocation(programID, "a_offset");
    int32_t colorLocation    = ctx.getAttribLocation(programID, "a_color");

    float cellW                       = 2.0f / (float)m_gridSide;
    float cellH                       = 2.0f / (float)m_gridSide;
    const tcu::Vec4 vertexPositions[] = {
        tcu::Vec4(0, 0, 0, 1),     tcu::Vec4(cellW, 0, 0, 1), tcu::Vec4(0, cellH, 0, 1),

        tcu::Vec4(0, cellH, 0, 1), tcu::Vec4(cellW, 0, 0, 1), tcu::Vec4(cellW, cellH, 0, 1),
    };

    const uint16_t indices[] = {0, 4, 3, 2, 1, 5};

    std::vector<tcu::Vec4> offsets;
    for (int x = 0; x < m_gridSide; ++x)
        for (int y = 0; y < m_gridSide; ++y)
            offsets.push_back(tcu::Vec4((float)x * cellW - 1.0f, (float)y * cellW - 1.0f, 0, 0));

    std::vector<tcu::Vec4> colors;
    for (int x = 0; x < m_gridSide; ++x)
        for (int y = 0; y < m_gridSide; ++y)
            colors.push_back(((x + y) % 2 == 0) ? (green) : (yellow));

    ctx.genVertexArrays(1, &vaoID);
    ctx.bindVertexArray(vaoID);

    ctx.genBuffers(1, &positionBuf);
    ctx.bindBuffer(GL_ARRAY_BUFFER, positionBuf);
    ctx.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    ctx.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    ctx.vertexAttribDivisor(posLocation, 0);
    ctx.enableVertexAttribArray(posLocation);

    ctx.genBuffers(1, &offsetBuf);
    ctx.bindBuffer(GL_ARRAY_BUFFER, offsetBuf);
    ctx.bufferData(GL_ARRAY_BUFFER, offsets.size() * sizeof(tcu::Vec4), &offsets[0], GL_STATIC_DRAW);
    ctx.vertexAttribPointer(offsetLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    ctx.vertexAttribDivisor(offsetLocation, 1);
    ctx.enableVertexAttribArray(offsetLocation);

    ctx.genBuffers(1, &colorBuf);
    ctx.bindBuffer(GL_ARRAY_BUFFER, colorBuf);
    ctx.bufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(tcu::Vec4), &colors[0], GL_STATIC_DRAW);
    ctx.vertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    ctx.vertexAttribDivisor(colorLocation, 1);
    ctx.enableVertexAttribArray(colorLocation);

    if (m_useIndices)
    {
        ctx.genBuffers(1, &indexBuf);
        ctx.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf);
        ctx.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    }

    ctx.genBuffers(1, &drawIndirectBuf);
    ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, drawIndirectBuf);

    if (m_useIndices)
    {
        DrawElementsCommand command;
        command.count              = 6;
        command.primCount          = m_gridSide * m_gridSide;
        command.firstIndex         = 0;
        command.baseVertex         = 0;
        command.reservedMustBeZero = 0;

        ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(command), &command, GL_STATIC_DRAW);
    }
    else
    {
        DrawArraysCommand command;
        command.count              = 6;
        command.primCount          = m_gridSide * m_gridSide;
        command.first              = 0;
        command.reservedMustBeZero = 0;

        ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(command), &command, GL_STATIC_DRAW);
    }

    ctx.clearColor(0, 0, 0, 1);
    ctx.clear(GL_COLOR_BUFFER_BIT);

    ctx.viewport(0, 0, dst.getWidth(), dst.getHeight());

    ctx.useProgram(programID);
    if (m_useIndices)
        ctx.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, DE_NULL);
    else
        ctx.drawArraysIndirect(GL_TRIANGLES, DE_NULL);
    ctx.useProgram(0);

    glu::checkError(ctx.getError(), "", __FILE__, __LINE__);

    ctx.deleteBuffers(1, &drawIndirectBuf);
    if (m_useIndices)
        ctx.deleteBuffers(1, &indexBuf);
    ctx.deleteBuffers(1, &colorBuf);
    ctx.deleteBuffers(1, &offsetBuf);
    ctx.deleteBuffers(1, &positionBuf);
    ctx.deleteVertexArrays(1, &vaoID);
    ctx.deleteProgram(programID);

    ctx.finish();
    ctx.readPixels(dst, 0, 0, dst.getWidth(), dst.getHeight());

    glu::checkError(ctx.getError(), "", __FILE__, __LINE__);
}

class InstancingGroup : public TestCaseGroup
{
public:
    InstancingGroup(Context &context, const char *name, const char *descr);
    ~InstancingGroup(void);

    void init(void);
};

InstancingGroup::InstancingGroup(Context &context, const char *name, const char *descr)
    : TestCaseGroup(context, name, descr)
{
}

InstancingGroup::~InstancingGroup(void)
{
}

void InstancingGroup::init(void)
{
    const int gridWidths[] = {
        2, 5, 10, 32, 100,
    };

    // drawArrays
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(gridWidths); ++ndx)
    {
        const std::string name = std::string("draw_arrays_indirect_grid_") + de::toString(gridWidths[ndx]) + "x" +
                                 de::toString(gridWidths[ndx]);
        const std::string desc = std::string("DrawArraysIndirect, Grid size ") + de::toString(gridWidths[ndx]) + "x" +
                                 de::toString(gridWidths[ndx]);

        this->addChild(new InstancedGridRenderTest(m_context, name.c_str(), desc.c_str(), gridWidths[ndx], false));
    }

    // drawElements
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(gridWidths); ++ndx)
    {
        const std::string name = std::string("draw_elements_indirect_grid_") + de::toString(gridWidths[ndx]) + "x" +
                                 de::toString(gridWidths[ndx]);
        const std::string desc = std::string("DrawElementsIndirect, Grid size ") + de::toString(gridWidths[ndx]) + "x" +
                                 de::toString(gridWidths[ndx]);

        this->addChild(new InstancedGridRenderTest(m_context, name.c_str(), desc.c_str(), gridWidths[ndx], true));
    }
}

class ComputeShaderGeneratedCase : public TestCase
{
public:
    enum DrawMethod
    {
        DRAWMETHOD_DRAWARRAYS,
        DRAWMETHOD_DRAWELEMENTS,
        DRAWMETHOD_LAST
    };

    ComputeShaderGeneratedCase(Context &context, const char *name, const char *desc, DrawMethod method, bool computeCmd,
                               bool computeData, bool computeIndices, int gridSize, int drawCallCount);
    ~ComputeShaderGeneratedCase(void);
    void init(void);
    void deinit(void);

    IterateResult iterate(void);
    std::string genComputeSource(bool computeCmd, bool computeData, bool computeIndices) const;

private:
    void createDrawCommand(void);
    void createDrawData(void);
    void createDrawIndices(void);

    virtual void runComputeShader(void) = 0;
    void renderTo(tcu::Surface &image);

protected:
    uint32_t calcDrawBufferSize(void) const;
    uint32_t calcIndexBufferSize(void) const;

    const DrawMethod m_drawMethod;
    const bool m_computeCmd;
    const bool m_computeData;
    const bool m_computeIndices;
    const int m_commandSize;
    const int m_numDrawCmds;
    const int m_gridSize;

    glw::GLuint m_cmdBufferID;
    glw::GLuint m_dataBufferID;
    glw::GLuint m_indexBufferID;

private:
    glu::ShaderProgram *m_shaderProgram;
};

ComputeShaderGeneratedCase::ComputeShaderGeneratedCase(Context &context, const char *name, const char *desc,
                                                       DrawMethod method, bool computeCmd, bool computeData,
                                                       bool computeIndices, int gridSize, int drawCallCount)
    : TestCase(context, name, desc)
    , m_drawMethod(method)
    , m_computeCmd(computeCmd)
    , m_computeData(computeData)
    , m_computeIndices(computeIndices)
    , m_commandSize((method == DRAWMETHOD_DRAWARRAYS) ? ((int)sizeof(DrawArraysCommand)) :
                                                        ((int)sizeof(DrawElementsCommand)))
    , m_numDrawCmds(drawCallCount)
    , m_gridSize(gridSize)
    , m_cmdBufferID(0)
    , m_dataBufferID(0)
    , m_indexBufferID(0)
    , m_shaderProgram(DE_NULL)
{
    const int triangleCount = m_gridSize * m_gridSize * 2;

    DE_ASSERT(method < DRAWMETHOD_LAST);
    DE_ASSERT(!computeIndices || method == DRAWMETHOD_DRAWELEMENTS);
    DE_ASSERT(triangleCount % m_numDrawCmds == 0);
    DE_UNREF(triangleCount);
}

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

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

    // generate basic shader

    m_shaderProgram = new glu::ShaderProgram(m_context.getRenderContext(),
                                             glu::ProgramSources() << glu::VertexSource(s_colorVertexShaderSource)
                                                                   << glu::FragmentSource(s_colorFragmentShaderSource));
    m_testCtx.getLog() << *m_shaderProgram;

    if (!m_shaderProgram->isOk())
        throw tcu::TestError("Failed to compile shader.");

    // gen buffers
    gl.genBuffers(1, &m_cmdBufferID);
    gl.genBuffers(1, &m_dataBufferID);
    gl.genBuffers(1, &m_indexBufferID);

    // check the SSBO buffers are of legal size
    {
        const uint64_t drawBufferElementSize  = sizeof(tcu::Vec4);
        const uint64_t indexBufferElementSize = sizeof(uint32_t);
        const int commandBufferSize           = m_commandSize * m_numDrawCmds;
        int64_t maxSSBOSize                   = 0;

        gl.getInteger64v(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxSSBOSize);

        if (m_computeData && (uint64_t)calcDrawBufferSize() * drawBufferElementSize > (uint64_t)maxSSBOSize)
            throw tcu::NotSupportedError("GL_MAX_SHADER_STORAGE_BLOCK_SIZE is too small for vertex attrib buffers");
        if (m_computeIndices && (uint64_t)calcIndexBufferSize() * indexBufferElementSize > (uint64_t)maxSSBOSize)
            throw tcu::NotSupportedError("GL_MAX_SHADER_STORAGE_BLOCK_SIZE is too small for index buffers");
        if (m_computeCmd && (uint64_t)commandBufferSize > (uint64_t)maxSSBOSize)
            throw tcu::NotSupportedError("GL_MAX_SHADER_STORAGE_BLOCK_SIZE is too small for command buffers");
    }
}

void ComputeShaderGeneratedCase::deinit(void)
{
    if (m_cmdBufferID)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_cmdBufferID);
        m_cmdBufferID = 0;
    }
    if (m_dataBufferID)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_dataBufferID);
        m_dataBufferID = 0;
    }
    if (m_indexBufferID)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBufferID);
        m_indexBufferID = 0;
    }

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

ComputeShaderGeneratedCase::IterateResult ComputeShaderGeneratedCase::iterate(void)
{
    const int renderTargetWidth  = de::min(1024, m_context.getRenderTarget().getWidth());
    const int renderTargetHeight = de::min(1024, m_context.getRenderTarget().getHeight());
    const glw::Functions &gl     = m_context.getRenderContext().getFunctions();
    tcu::Surface surface(renderTargetWidth, renderTargetHeight);

    m_testCtx.getLog() << tcu::TestLog::Message << "Preparing to draw " << m_gridSize << " x " << m_gridSize << " grid."
                       << tcu::TestLog::EndMessage;

    try
    {
        // Gen command buffer
        if (!m_computeCmd)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Uploading draw command buffer." << tcu::TestLog::EndMessage;
            createDrawCommand();
        }

        // Gen data buffer
        if (!m_computeData)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Uploading draw data buffer." << tcu::TestLog::EndMessage;
            createDrawData();
        }

        // Gen index buffer
        if (!m_computeIndices && m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Uploading draw index buffer." << tcu::TestLog::EndMessage;
            createDrawIndices();
        }

        // Run compute shader
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Filling following buffers using compute shader:\n"
                               << ((m_computeCmd) ? ("\tcommand buffer\n") : (""))
                               << ((m_computeData) ? ("\tdata buffer\n") : (""))
                               << ((m_computeIndices) ? ("\tindex buffer\n") : ("")) << tcu::TestLog::EndMessage;
            runComputeShader();
        }

        // Ensure data is written to the buffers before we try to read it
        {
            const glw::GLuint barriers = ((m_computeCmd) ? (GL_COMMAND_BARRIER_BIT) : (0)) |
                                         ((m_computeData) ? (GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT) : (0)) |
                                         ((m_computeIndices) ? (GL_ELEMENT_ARRAY_BARRIER_BIT) : (0));

            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Memory barrier. Barriers = " << glu::getMemoryBarrierFlagsStr(barriers)
                               << tcu::TestLog::EndMessage;
            gl.memoryBarrier(barriers);
        }

        // Draw from buffers

        m_testCtx.getLog() << tcu::TestLog::Message << "Drawing from buffers with " << m_numDrawCmds << " draw call(s)."
                           << tcu::TestLog::EndMessage;
        renderTo(surface);
    }
    catch (glu::OutOfMemoryError &)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Got GL_OUT_OF_MEMORY." << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Got GL_OUT_OF_MEMORY");
        m_testCtx.setTerminateAfter(true); // Do not rely on implementation to be able to recover from OOM
        return STOP;
    }

    // verify image
    // \note the green/yellow pattern is only for clarity. The test will only verify that all grid cells were drawn by looking for anything non-green/yellow.
    if (verifyImageYellowGreen(surface, m_testCtx.getLog()))
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result image invalid");
    return STOP;
}

std::string ComputeShaderGeneratedCase::genComputeSource(bool computeCmd, bool computeData, bool computeIndices) const
{
    const int cmdLayoutBinding   = 0;
    const int dataLayoutBinding  = (computeCmd) ? (1) : (0);
    const int indexLayoutBinding = (computeCmd && computeData) ? (2) : (computeCmd || computeData) ? (1) : (0);

    std::ostringstream buf;

    buf << "#version 310 es\n\n"
        << "precision highp int;\n"
        << "precision highp float;\n\n";

    if (computeCmd && m_drawMethod == DRAWMETHOD_DRAWARRAYS)
        buf << "struct DrawArraysIndirectCommand {\n"
            << "    uint count;\n"
            << "    uint primCount;\n"
            << "    uint first;\n"
            << "    uint reservedMustBeZero;\n"
            << "};\n\n";
    else if (computeCmd && m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
        buf << "struct DrawElementsIndirectCommand {\n"
            << "    uint count;\n"
            << "    uint primCount;\n"
            << "    uint firstIndex;\n"
            << "    int  baseVertex;\n"
            << "    uint reservedMustBeZero;\n"
            << "};\n\n";

    buf << "layout(local_size_x = 1, local_size_y = 1) in;\n"
        << "layout(std430) buffer;\n\n";

    if (computeCmd)
        buf << "layout(binding = " << cmdLayoutBinding << ") writeonly buffer CommandBuffer {\n"
            << "    "
            << ((m_drawMethod == DRAWMETHOD_DRAWARRAYS) ? ("DrawArraysIndirectCommand") :
                                                          ("DrawElementsIndirectCommand"))
            << " commands[];\n"
            << "};\n";
    if (computeData)
        buf << "layout(binding = " << dataLayoutBinding << ") writeonly buffer DataBuffer {\n"
            << "    vec4 attribs[];\n"
            << "};\n";
    if (computeIndices)
        buf << "layout(binding = " << indexLayoutBinding << ") writeonly buffer IndexBuffer {\n"
            << "    uint indices[];\n"
            << "};\n";

    buf << "\n"
        << "void main() {\n"
        << "    const uint gridSize      = " << m_gridSize << "u;\n"
        << "    const uint triangleCount = gridSize * gridSize * 2u;\n"
        << "\n";

    if (computeCmd)
    {
        buf << "    // command\n"
            << "    if (gl_GlobalInvocationID.x < " << m_numDrawCmds
            << "u && gl_GlobalInvocationID.y == 0u && gl_GlobalInvocationID.z == 0u) {\n"
            << "        const uint numDrawCallTris = triangleCount / " << m_numDrawCmds << "u;\n"
            << "        uint firstTri              = gl_GlobalInvocationID.x * numDrawCallTris;\n\n"
            << "        commands[gl_GlobalInvocationID.x].count                 = numDrawCallTris*3u;\n"
            << "        commands[gl_GlobalInvocationID.x].primCount             = 1u;\n";

        if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
        {
            buf << "        commands[gl_GlobalInvocationID.x].first                 = firstTri*3u;\n";
        }
        else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
        {
            buf << "        commands[gl_GlobalInvocationID.x].firstIndex            = firstTri*3u;\n";
            buf << "        commands[gl_GlobalInvocationID.x].baseVertex            = 0;\n";
        }

        buf << "        commands[gl_GlobalInvocationID.x].reservedMustBeZero    = 0u;\n"
            << "    }\n"
            << "\n";
    }

    if (computeData)
    {
        buf << "    // vertex attribs\n"
            << "    const vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
            << "    const vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n";

        if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
        {
            buf << "    if (gl_GlobalInvocationID.x < gridSize && gl_GlobalInvocationID.y < gridSize && "
                   "gl_GlobalInvocationID.z == 0u) {\n"
                << "        uint        y           = gl_GlobalInvocationID.x;\n"
                << "        uint        x           = gl_GlobalInvocationID.y;\n"
                << "        float       posX        = (float(x)    / float(gridSize)) * 2.0 - 1.0;\n"
                << "        float       posXp1      = (float(x+1u) / float(gridSize)) * 2.0 - 1.0;\n"
                << "        float       posY        = (float(y)    / float(gridSize)) * 2.0 - 1.0;\n"
                << "        float       posYp1      = (float(y+1u) / float(gridSize)) * 2.0 - 1.0;\n"
                << "        vec4        color       = ((x + y)%2u != 0u) ? (yellow) : (green);\n"
                << "\n"
                << "        attribs[((y * gridSize + x) * 6u + 0u) * 2u + 0u] = vec4(posX,   posY,   0.0, 1.0);\n"
                << "        attribs[((y * gridSize + x) * 6u + 1u) * 2u + 0u] = vec4(posXp1, posY,   0.0, 1.0);\n"
                << "        attribs[((y * gridSize + x) * 6u + 2u) * 2u + 0u] = vec4(posXp1, posYp1, 0.0, 1.0);\n"
                << "        attribs[((y * gridSize + x) * 6u + 3u) * 2u + 0u] = vec4(posX,   posY,   0.0, 1.0);\n"
                << "        attribs[((y * gridSize + x) * 6u + 4u) * 2u + 0u] = vec4(posXp1, posYp1, 0.0, 1.0);\n"
                << "        attribs[((y * gridSize + x) * 6u + 5u) * 2u + 0u] = vec4(posX,   posYp1, 0.0, 1.0);\n"
                << "\n"
                << "        attribs[((y * gridSize + x) * 6u + 0u) * 2u + 1u] = color;\n"
                << "        attribs[((y * gridSize + x) * 6u + 1u) * 2u + 1u] = color;\n"
                << "        attribs[((y * gridSize + x) * 6u + 2u) * 2u + 1u] = color;\n"
                << "        attribs[((y * gridSize + x) * 6u + 3u) * 2u + 1u] = color;\n"
                << "        attribs[((y * gridSize + x) * 6u + 4u) * 2u + 1u] = color;\n"
                << "        attribs[((y * gridSize + x) * 6u + 5u) * 2u + 1u] = color;\n"
                << "    }\n";
        }
        else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
        {
            buf << "    if (gl_GlobalInvocationID.x < gridSize+1u && gl_GlobalInvocationID.y < gridSize+1u && "
                   "gl_GlobalInvocationID.z == 0u) {\n"
                << "        uint        y           = gl_GlobalInvocationID.x;\n"
                << "        uint        x           = gl_GlobalInvocationID.y;\n"
                << "        float       posX        = (float(x) / float(gridSize)) * 2.0 - 1.0;\n"
                << "        float       posY        = (float(y) / float(gridSize)) * 2.0 - 1.0;\n"
                << "\n"
                << "        attribs[(y * (gridSize+1u) + x) * 4u + 0u] = vec4(posX, posY, 0.0, 1.0);\n"
                << "        attribs[(y * (gridSize+1u) + x) * 4u + 1u] = green;\n"
                << "        attribs[(y * (gridSize+1u) + x) * 4u + 2u] = vec4(posX, posY, 0.0, 1.0);\n"
                << "        attribs[(y * (gridSize+1u) + x) * 4u + 3u] = yellow;\n"
                << "    }\n";
        }

        buf << "\n";
    }

    if (computeIndices)
    {
        buf << "    // indices\n"
            << "    if (gl_GlobalInvocationID.x < gridSize && gl_GlobalInvocationID.y < gridSize && "
               "gl_GlobalInvocationID.z == 0u) {\n"
            << "        uint    y       = gl_GlobalInvocationID.x;\n"
            << "        uint    x       = gl_GlobalInvocationID.y;\n"
            << "        uint    color   = ((x + y)%2u);\n"
            << "\n"
            << "        indices[(y * gridSize + x) * 6u + 0u] = ((y+0u) * (gridSize+1u) + (x+0u)) * 2u + color;\n"
            << "        indices[(y * gridSize + x) * 6u + 1u] = ((y+1u) * (gridSize+1u) + (x+0u)) * 2u + color;\n"
            << "        indices[(y * gridSize + x) * 6u + 2u] = ((y+1u) * (gridSize+1u) + (x+1u)) * 2u + color;\n"
            << "        indices[(y * gridSize + x) * 6u + 3u] = ((y+0u) * (gridSize+1u) + (x+0u)) * 2u + color;\n"
            << "        indices[(y * gridSize + x) * 6u + 4u] = ((y+1u) * (gridSize+1u) + (x+1u)) * 2u + color;\n"
            << "        indices[(y * gridSize + x) * 6u + 5u] = ((y+0u) * (gridSize+1u) + (x+1u)) * 2u + color;\n"
            << "    }\n"
            << "\n";
    }

    buf << "}\n";

    return buf.str();
}

void ComputeShaderGeneratedCase::createDrawCommand(void)
{
    const glw::Functions &gl       = m_context.getRenderContext().getFunctions();
    const int triangleCount        = m_gridSize * m_gridSize * 2;
    const uint32_t numDrawCallTris = triangleCount / m_numDrawCmds;

    if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
    {
        std::vector<DrawArraysCommand> cmds;

        for (int ndx = 0; ndx < m_numDrawCmds; ++ndx)
        {
            const uint32_t firstTri = ndx * numDrawCallTris;
            DrawArraysCommand data;

            data.count              = numDrawCallTris * 3;
            data.primCount          = 1;
            data.first              = firstTri * 3;
            data.reservedMustBeZero = 0;

            cmds.push_back(data);
        }

        DE_ASSERT((int)(sizeof(DrawArraysCommand) * cmds.size()) == m_numDrawCmds * m_commandSize);

        gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);
        gl.bufferData(GL_DRAW_INDIRECT_BUFFER, (glw::GLsizeiptr)(sizeof(DrawArraysCommand) * cmds.size()), &cmds[0],
                      GL_STATIC_DRAW);
    }
    else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
    {
        std::vector<DrawElementsCommand> cmds;

        for (int ndx = 0; ndx < m_numDrawCmds; ++ndx)
        {
            const uint32_t firstTri = ndx * numDrawCallTris;
            DrawElementsCommand data;

            data.count              = numDrawCallTris * 3;
            data.primCount          = 1;
            data.firstIndex         = firstTri * 3;
            data.baseVertex         = 0;
            data.reservedMustBeZero = 0;

            cmds.push_back(data);
        }

        DE_ASSERT((int)(sizeof(DrawElementsCommand) * cmds.size()) == m_numDrawCmds * m_commandSize);

        gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);
        gl.bufferData(GL_DRAW_INDIRECT_BUFFER, (glw::GLsizeiptr)(sizeof(DrawElementsCommand) * cmds.size()), &cmds[0],
                      GL_STATIC_DRAW);
    }
    else
        DE_ASSERT(false);

    glu::checkError(gl.getError(), "create draw command", __FILE__, __LINE__);
}

void ComputeShaderGeneratedCase::createDrawData(void)
{
    const tcu::Vec4 yellow(1.0f, 1.0f, 0.0f, 1.0f);
    const tcu::Vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
    {
        // Store elements in the order they are drawn. Interleave color.
        std::vector<tcu::Vec4> buffer(m_gridSize * m_gridSize * 6 * 2);

        DE_ASSERT(buffer.size() == calcDrawBufferSize());

        for (int y = 0; y < m_gridSize; ++y)
            for (int x = 0; x < m_gridSize; ++x)
            {
                const float posX       = ((float)x / (float)m_gridSize) * 2.0f - 1.0f;
                const float posY       = ((float)y / (float)m_gridSize) * 2.0f - 1.0f;
                const float cellSize   = 2.0f / (float)m_gridSize;
                const tcu::Vec4 &color = ((x + y) % 2) ? (yellow) : (green);

                buffer[((y * m_gridSize + x) * 6 + 0) * 2 + 0] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
                buffer[((y * m_gridSize + x) * 6 + 1) * 2 + 0] = tcu::Vec4(posX + cellSize, posY, 0.0f, 1.0f);
                buffer[((y * m_gridSize + x) * 6 + 2) * 2 + 0] =
                    tcu::Vec4(posX + cellSize, posY + cellSize, 0.0f, 1.0f);
                buffer[((y * m_gridSize + x) * 6 + 3) * 2 + 0] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
                buffer[((y * m_gridSize + x) * 6 + 4) * 2 + 0] =
                    tcu::Vec4(posX + cellSize, posY + cellSize, 0.0f, 1.0f);
                buffer[((y * m_gridSize + x) * 6 + 5) * 2 + 0] = tcu::Vec4(posX, posY + cellSize, 0.0f, 1.0f);

                buffer[((y * m_gridSize + x) * 6 + 0) * 2 + 1] = color;
                buffer[((y * m_gridSize + x) * 6 + 1) * 2 + 1] = color;
                buffer[((y * m_gridSize + x) * 6 + 2) * 2 + 1] = color;
                buffer[((y * m_gridSize + x) * 6 + 3) * 2 + 1] = color;
                buffer[((y * m_gridSize + x) * 6 + 4) * 2 + 1] = color;
                buffer[((y * m_gridSize + x) * 6 + 5) * 2 + 1] = color;
            }

        gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBufferID);
        gl.bufferData(GL_ARRAY_BUFFER, (int)(buffer.size() * sizeof(tcu::Vec4)), buffer[0].getPtr(), GL_STATIC_DRAW);
    }
    else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
    {
        // Elements are indexed by index buffer. Interleave color. Two vertices per position since 2 colors

        std::vector<tcu::Vec4> buffer((m_gridSize + 1) * (m_gridSize + 1) * 4);

        DE_ASSERT(buffer.size() == calcDrawBufferSize());

        for (int y = 0; y < m_gridSize + 1; ++y)
            for (int x = 0; x < m_gridSize + 1; ++x)
            {
                const float posX = ((float)x / (float)m_gridSize) * 2.0f - 1.0f;
                const float posY = ((float)y / (float)m_gridSize) * 2.0f - 1.0f;

                buffer[(y * (m_gridSize + 1) + x) * 4 + 0] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
                buffer[(y * (m_gridSize + 1) + x) * 4 + 1] = green;
                buffer[(y * (m_gridSize + 1) + x) * 4 + 2] = tcu::Vec4(posX, posY, 0.0f, 1.0f);
                buffer[(y * (m_gridSize + 1) + x) * 4 + 3] = yellow;
            }

        gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBufferID);
        gl.bufferData(GL_ARRAY_BUFFER, (int)(buffer.size() * sizeof(tcu::Vec4)), buffer[0].getPtr(), GL_STATIC_DRAW);
    }
    else
        DE_ASSERT(false);

    glu::checkError(gl.getError(), "", __FILE__, __LINE__);
}

void ComputeShaderGeneratedCase::createDrawIndices(void)
{
    DE_ASSERT(m_drawMethod == DRAWMETHOD_DRAWELEMENTS);

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    std::vector<uint32_t> buffer(m_gridSize * m_gridSize * 6);

    DE_ASSERT(buffer.size() == calcIndexBufferSize());

    for (int y = 0; y < m_gridSize; ++y)
        for (int x = 0; x < m_gridSize; ++x)
        {
            const int color = ((x + y) % 2);

            buffer[(y * m_gridSize + x) * 6 + 0] = ((y + 0) * (m_gridSize + 1) + (x + 0)) * 2 + color;
            buffer[(y * m_gridSize + x) * 6 + 1] = ((y + 1) * (m_gridSize + 1) + (x + 0)) * 2 + color;
            buffer[(y * m_gridSize + x) * 6 + 2] = ((y + 1) * (m_gridSize + 1) + (x + 1)) * 2 + color;
            buffer[(y * m_gridSize + x) * 6 + 3] = ((y + 0) * (m_gridSize + 1) + (x + 0)) * 2 + color;
            buffer[(y * m_gridSize + x) * 6 + 4] = ((y + 1) * (m_gridSize + 1) + (x + 1)) * 2 + color;
            buffer[(y * m_gridSize + x) * 6 + 5] = ((y + 0) * (m_gridSize + 1) + (x + 1)) * 2 + color;
        }

    gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferID);
    gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(buffer.size() * sizeof(uint32_t)), &buffer[0], GL_STATIC_DRAW);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);
}

void ComputeShaderGeneratedCase::renderTo(tcu::Surface &dst)
{
    const glw::Functions &gl  = m_context.getRenderContext().getFunctions();
    const int32_t positionLoc = gl.getAttribLocation(m_shaderProgram->getProgram(), "a_position");
    const int32_t colorLoc    = gl.getAttribLocation(m_shaderProgram->getProgram(), "a_color");
    uint32_t vaoID            = 0;

    gl.genVertexArrays(1, &vaoID);
    gl.bindVertexArray(vaoID);

    // Setup buffers

    gl.bindBuffer(GL_ARRAY_BUFFER, m_dataBufferID);
    gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 8 * (int)sizeof(float), DE_NULL);
    gl.vertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 8 * (int)sizeof(float),
                           glu::BufferOffsetAsPointer(4 * sizeof(float)));
    gl.enableVertexAttribArray(positionLoc);
    gl.enableVertexAttribArray(colorLoc);

    DE_ASSERT(positionLoc != -1);
    DE_ASSERT(colorLoc != -1);

    if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
        gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferID);

    gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_cmdBufferID);

    // draw

    gl.clearColor(0, 0, 0, 1);
    gl.clear(GL_COLOR_BUFFER_BIT);
    gl.viewport(0, 0, dst.getWidth(), dst.getHeight());

    gl.useProgram(m_shaderProgram->getProgram());
    for (int drawCmdNdx = 0; drawCmdNdx < m_numDrawCmds; ++drawCmdNdx)
    {
        const void *offset = glu::BufferOffsetAsPointer(drawCmdNdx * m_commandSize);

        if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
            gl.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, offset);
        else if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
            gl.drawArraysIndirect(GL_TRIANGLES, offset);
        else
            DE_ASSERT(false);
    }
    gl.useProgram(0);

    // free

    gl.deleteVertexArrays(1, &vaoID);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    gl.finish();
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

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

uint32_t ComputeShaderGeneratedCase::calcDrawBufferSize(void) const
{
    // returns size in "vec4"s
    if (m_drawMethod == DRAWMETHOD_DRAWARRAYS)
        return m_gridSize * m_gridSize * 6 * 2;
    else if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
        return (m_gridSize + 1) * (m_gridSize + 1) * 4;
    else
        DE_ASSERT(false);

    return 0;
}

uint32_t ComputeShaderGeneratedCase::calcIndexBufferSize(void) const
{
    if (m_drawMethod == DRAWMETHOD_DRAWELEMENTS)
        return m_gridSize * m_gridSize * 6;
    else
        return 0;
}

class ComputeShaderGeneratedCombinedCase : public ComputeShaderGeneratedCase
{
public:
    ComputeShaderGeneratedCombinedCase(Context &context, const char *name, const char *desc, DrawMethod method,
                                       bool computeCmd, bool computeData, bool computeIndices, int gridSize,
                                       int numDrawCalls);
    ~ComputeShaderGeneratedCombinedCase(void);

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

private:
    void runComputeShader(void);

    glu::ShaderProgram *m_computeProgram;
};

ComputeShaderGeneratedCombinedCase::ComputeShaderGeneratedCombinedCase(Context &context, const char *name,
                                                                       const char *desc, DrawMethod method,
                                                                       bool computeCmd, bool computeData,
                                                                       bool computeIndices, int gridSize,
                                                                       int numDrawCalls)
    : ComputeShaderGeneratedCase(context, name, desc, method, computeCmd, computeData, computeIndices, gridSize,
                                 numDrawCalls)
    , m_computeProgram(DE_NULL)
{
}

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

void ComputeShaderGeneratedCombinedCase::init(void)
{
    // generate compute shader

    m_computeProgram = new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::ComputeSource(genComputeSource(m_computeCmd, m_computeData, m_computeIndices)));
    m_testCtx.getLog() << *m_computeProgram;

    if (!m_computeProgram->isOk())
        throw tcu::TestError("Failed to compile compute shader.");

    // init parent
    ComputeShaderGeneratedCase::init();
}

void ComputeShaderGeneratedCombinedCase::deinit(void)
{
    // deinit parent
    ComputeShaderGeneratedCase::deinit();

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

void ComputeShaderGeneratedCombinedCase::runComputeShader(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const bool indexed       = (m_drawMethod == DRAWMETHOD_DRAWELEMENTS);
    const tcu::IVec3 nullSize(0, 0, 0);
    const tcu::IVec3 commandDispatchSize = (m_computeCmd) ? (tcu::IVec3(m_numDrawCmds, 1, 1)) : (nullSize);
    const tcu::IVec3 drawElementsDataBufferDispatchSize =
        (m_computeData) ? (tcu::IVec3(m_gridSize + 1, m_gridSize + 1, 1)) : (nullSize);
    const tcu::IVec3 drawArraysDataBufferDispatchSize =
        (m_computeData) ? (tcu::IVec3(m_gridSize, m_gridSize, 1)) : (nullSize);
    const tcu::IVec3 indexBufferDispatchSize =
        (m_computeIndices && indexed) ? (tcu::IVec3(m_gridSize, m_gridSize, 1)) : (nullSize);

    const tcu::IVec3 dataBufferDispatchSize = (m_drawMethod == DRAWMETHOD_DRAWELEMENTS) ?
                                                  (drawElementsDataBufferDispatchSize) :
                                                  (drawArraysDataBufferDispatchSize);
    const tcu::IVec3 dispatchSize =
        tcu::max(tcu::max(commandDispatchSize, dataBufferDispatchSize), indexBufferDispatchSize);

    gl.useProgram(m_computeProgram->getProgram());
    glu::checkError(gl.getError(), "use compute shader", __FILE__, __LINE__);

    // setup buffers

    if (m_computeCmd)
    {
        const int bindingPoint = 0;
        const int bufferSize   = m_commandSize * m_numDrawCmds;

        m_testCtx.getLog() << tcu::TestLog::Message << "Binding command buffer to binding point " << bindingPoint
                           << tcu::TestLog::EndMessage;
        gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_cmdBufferID);

        m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for command buffer, size "
                           << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
    }

    if (m_computeData)
    {
        const int bindingPoint = (m_computeCmd) ? (1) : (0);
        const int bufferSize   = (int)(calcDrawBufferSize() * sizeof(tcu::Vec4));

        m_testCtx.getLog() << tcu::TestLog::Message << "Binding data buffer to binding point " << bindingPoint
                           << tcu::TestLog::EndMessage;
        gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_dataBufferID);

        m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for data buffer, size "
                           << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
    }

    if (m_computeIndices)
    {
        const int bindingPoint = (m_computeCmd && m_computeData) ? (2) : (m_computeCmd || m_computeData) ? (1) : (0);
        const int bufferSize   = (int)(calcIndexBufferSize() * sizeof(uint32_t));

        m_testCtx.getLog() << tcu::TestLog::Message << "Binding index buffer to binding point " << bindingPoint
                           << tcu::TestLog::EndMessage;
        gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_indexBufferID);

        m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for index buffer, size "
                           << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);
    }

    glu::checkError(gl.getError(), "setup buffers", __FILE__, __LINE__);

    // calculate

    m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching compute, size = " << dispatchSize
                       << tcu::TestLog::EndMessage;
    gl.dispatchCompute(dispatchSize.x(), dispatchSize.y(), dispatchSize.z());

    glu::checkError(gl.getError(), "calculate", __FILE__, __LINE__);
}

class ComputeShaderGeneratedSeparateCase : public ComputeShaderGeneratedCase
{
public:
    ComputeShaderGeneratedSeparateCase(Context &context, const char *name, const char *desc, DrawMethod method,
                                       bool computeCmd, bool computeData, bool computeIndices, int gridSize,
                                       int numDrawCalls);
    ~ComputeShaderGeneratedSeparateCase(void);

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

private:
    std::string genCmdComputeSource(void);
    std::string genDataComputeSource(void);
    std::string genIndexComputeSource(void);
    void runComputeShader(void);

    glu::ShaderProgram *m_computeCmdProgram;
    glu::ShaderProgram *m_computeDataProgram;
    glu::ShaderProgram *m_computeIndicesProgram;
};

ComputeShaderGeneratedSeparateCase::ComputeShaderGeneratedSeparateCase(Context &context, const char *name,
                                                                       const char *desc, DrawMethod method,
                                                                       bool computeCmd, bool computeData,
                                                                       bool computeIndices, int gridSize,
                                                                       int numDrawCalls)
    : ComputeShaderGeneratedCase(context, name, desc, method, computeCmd, computeData, computeIndices, gridSize,
                                 numDrawCalls)
    , m_computeCmdProgram(DE_NULL)
    , m_computeDataProgram(DE_NULL)
    , m_computeIndicesProgram(DE_NULL)
{
}

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

void ComputeShaderGeneratedSeparateCase::init(void)
{
    // generate cmd compute shader

    if (m_computeCmd)
    {
        m_computeCmdProgram = new glu::ShaderProgram(
            m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genCmdComputeSource()));
        m_testCtx.getLog() << *m_computeCmdProgram;

        if (!m_computeCmdProgram->isOk())
            throw tcu::TestError("Failed to compile command compute shader.");
    }

    // generate data compute shader

    if (m_computeData)
    {
        m_computeDataProgram = new glu::ShaderProgram(
            m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genDataComputeSource()));
        m_testCtx.getLog() << *m_computeDataProgram;

        if (!m_computeDataProgram->isOk())
            throw tcu::TestError("Failed to compile data compute shader.");
    }

    // generate index compute shader

    if (m_computeIndices)
    {
        m_computeIndicesProgram = new glu::ShaderProgram(
            m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(genIndexComputeSource()));
        m_testCtx.getLog() << *m_computeIndicesProgram;

        if (!m_computeIndicesProgram->isOk())
            throw tcu::TestError("Failed to compile data compute shader.");
    }

    // init parent
    ComputeShaderGeneratedCase::init();
}

void ComputeShaderGeneratedSeparateCase::deinit(void)
{
    // deinit parent
    ComputeShaderGeneratedCase::deinit();

    if (m_computeCmdProgram)
    {
        delete m_computeCmdProgram;
        m_computeCmdProgram = DE_NULL;
    }
    if (m_computeDataProgram)
    {
        delete m_computeDataProgram;
        m_computeDataProgram = DE_NULL;
    }
    if (m_computeIndicesProgram)
    {
        delete m_computeIndicesProgram;
        m_computeIndicesProgram = DE_NULL;
    }
}

std::string ComputeShaderGeneratedSeparateCase::genCmdComputeSource(void)
{
    return ComputeShaderGeneratedCase::genComputeSource(true, false, false);
}

std::string ComputeShaderGeneratedSeparateCase::genDataComputeSource(void)
{
    return ComputeShaderGeneratedCase::genComputeSource(false, true, false);
}

std::string ComputeShaderGeneratedSeparateCase::genIndexComputeSource(void)
{
    return ComputeShaderGeneratedCase::genComputeSource(false, false, true);
}

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

    // Compute command

    if (m_computeCmd)
    {
        const int bindingPoint = 0;
        const tcu::IVec3 commandDispatchSize(m_numDrawCmds, 1, 1);
        const int bufferSize = m_commandSize * m_numDrawCmds;

        gl.useProgram(m_computeCmdProgram->getProgram());

        // setup buffers

        m_testCtx.getLog() << tcu::TestLog::Message << "Binding command buffer to binding point " << bindingPoint
                           << tcu::TestLog::EndMessage;
        gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_cmdBufferID);

        m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for command buffer, size "
                           << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);

        // calculate

        m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching command compute, size = " << commandDispatchSize
                           << tcu::TestLog::EndMessage;
        gl.dispatchCompute(commandDispatchSize.x(), commandDispatchSize.y(), commandDispatchSize.z());

        glu::checkError(gl.getError(), "calculate cmd", __FILE__, __LINE__);
    }

    // Compute data

    if (m_computeData)
    {
        const int bindingPoint = 0;
        const tcu::IVec3 drawElementsDataBufferDispatchSize(m_gridSize + 1, m_gridSize + 1, 1);
        const tcu::IVec3 drawArraysDataBufferDispatchSize(m_gridSize, m_gridSize, 1);
        const tcu::IVec3 dataBufferDispatchSize = (m_drawMethod == DRAWMETHOD_DRAWELEMENTS) ?
                                                      (drawElementsDataBufferDispatchSize) :
                                                      (drawArraysDataBufferDispatchSize);
        const int bufferSize                    = (int)(calcDrawBufferSize() * sizeof(tcu::Vec4));

        gl.useProgram(m_computeDataProgram->getProgram());

        // setup buffers

        m_testCtx.getLog() << tcu::TestLog::Message << "Binding data buffer to binding point " << bindingPoint
                           << tcu::TestLog::EndMessage;
        gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_dataBufferID);

        m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for data buffer, size "
                           << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);

        // calculate

        m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching data compute, size = " << dataBufferDispatchSize
                           << tcu::TestLog::EndMessage;
        gl.dispatchCompute(dataBufferDispatchSize.x(), dataBufferDispatchSize.y(), dataBufferDispatchSize.z());

        glu::checkError(gl.getError(), "calculate data", __FILE__, __LINE__);
    }

    // Compute indices

    if (m_computeIndices)
    {
        const int bindingPoint = 0;
        const tcu::IVec3 indexBufferDispatchSize(m_gridSize, m_gridSize, 1);
        const int bufferSize = (int)(calcIndexBufferSize() * sizeof(uint32_t));

        DE_ASSERT(m_drawMethod == DRAWMETHOD_DRAWELEMENTS);

        gl.useProgram(m_computeIndicesProgram->getProgram());

        // setup buffers

        m_testCtx.getLog() << tcu::TestLog::Message << "Binding index buffer to binding point " << bindingPoint
                           << tcu::TestLog::EndMessage;
        gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingPoint, m_indexBufferID);

        m_testCtx.getLog() << tcu::TestLog::Message << "Allocating memory for index buffer, size "
                           << sizeToString(bufferSize) << "." << tcu::TestLog::EndMessage;
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_DRAW);

        // calculate

        m_testCtx.getLog() << tcu::TestLog::Message << "Dispatching index compute, size = " << indexBufferDispatchSize
                           << tcu::TestLog::EndMessage;
        gl.dispatchCompute(indexBufferDispatchSize.x(), indexBufferDispatchSize.y(), indexBufferDispatchSize.z());

        glu::checkError(gl.getError(), "calculate indices", __FILE__, __LINE__);
    }

    glu::checkError(gl.getError(), "post dispatch", __FILE__, __LINE__);
}

class ComputeShaderGeneratedGroup : public TestCaseGroup
{
public:
    ComputeShaderGeneratedGroup(Context &context, const char *name, const char *descr);
    ~ComputeShaderGeneratedGroup(void);

    void init(void);
};

ComputeShaderGeneratedGroup::ComputeShaderGeneratedGroup(Context &context, const char *name, const char *descr)
    : TestCaseGroup(context, name, descr)
{
}

ComputeShaderGeneratedGroup::~ComputeShaderGeneratedGroup(void)
{
}

void ComputeShaderGeneratedGroup::init(void)
{
    const int gridSize = 8;
    tcu::TestCaseGroup *const separateGroup =
        new tcu::TestCaseGroup(m_testCtx, "separate", "Use separate compute shaders for each buffer");
    tcu::TestCaseGroup *const combinedGroup =
        new tcu::TestCaseGroup(m_testCtx, "combined", "Use combined compute shader for all buffers");
    tcu::TestCaseGroup *const largeGroup = new tcu::TestCaseGroup(m_testCtx, "large", "Draw shapes with large buffers");

    this->addChild(separateGroup);
    this->addChild(combinedGroup);
    this->addChild(largeGroup);

    // .separate
    {
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawarrays_compute_cmd", "Command from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS, true, false, false, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawarrays_compute_data", "Data from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS, false, true, false, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawarrays_compute_cmd_and_data", "Command and data from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS, true, true, false, gridSize, 1));

        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawelements_compute_cmd", "Command from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true, false, false, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawelements_compute_data", "Data from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, false, true, false, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawelements_compute_indices", "Indices from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, false, false, true, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawelements_compute_cmd_and_data", "Command and data from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true, true, false, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawelements_compute_cmd_and_indices", "Command and indices from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true, false, true, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawelements_compute_data_and_indices", "Data and indices from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, false, true, true, gridSize, 1));
        separateGroup->addChild(new ComputeShaderGeneratedSeparateCase(
            m_context, "drawelements_compute_cmd_and_data_and_indices", "Command, data and indices from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true, true, true, gridSize, 1));
    }

    // .combined
    {
        combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(
            m_context, "drawarrays_compute_cmd_and_data", "Command and data from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS, true, true, false, gridSize, 1));
        combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(
            m_context, "drawelements_compute_cmd_and_data", "Command and data from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true, true, false, gridSize, 1));
        combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(
            m_context, "drawelements_compute_cmd_and_indices", "Command and indices from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true, false, true, gridSize, 1));
        combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(
            m_context, "drawelements_compute_data_and_indices", "Data and indices from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, false, true, true, gridSize, 1));
        combinedGroup->addChild(new ComputeShaderGeneratedCombinedCase(
            m_context, "drawelements_compute_cmd_and_data_and_indices", "Command, data and indices from compute shader",
            ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true, true, true, gridSize, 1));
    }

    // .large
    {
        struct TestSpec
        {
            int gridSize;
            int numDrawCommands;
        };
        struct TestMethod
        {
            ComputeShaderGeneratedCase::DrawMethod method;
            bool separateCompute;
        };

        static const TestSpec specs[] = {
            {100, 1},  // !< drawArrays array size ~ 1.9 MB
            {200, 1},  // !< drawArrays array size ~ 7.7 MB
            {500, 1},  // !< drawArrays array size ~ 48 MB
            {1000, 1}, // !< drawArrays array size ~ 192 MB
            {1200, 1}, // !< drawArrays array size ~ 277 MB
            {1500, 1}, // !< drawArrays array size ~ 430 MB

            {100, 8},  // !< drawArrays array size ~ 1.9 MB
            {200, 8},  // !< drawArrays array size ~ 7.7 MB
            {500, 8},  // !< drawArrays array size ~ 48 MB
            {1000, 8}, // !< drawArrays array size ~ 192 MB
            {1200, 8}, // !< drawArrays array size ~ 277 MB
            {1500, 8}, // !< drawArrays array size ~ 430 MB

            {100, 200},   // !< 50 cells per draw call
            {200, 800},   // !< 50 cells per draw call
            {500, 2500},  // !< 100 cells per draw call
            {1000, 5000}, // !< 250 cells per draw call
        };
        static const TestMethod methods[] = {
            {ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS, true},
            {ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS, false},
            {ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, true},
            {ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS, false},
        };

        for (int methodNdx = 0; methodNdx < DE_LENGTH_OF_ARRAY(methods); ++methodNdx)
            for (int specNdx = 0; specNdx < DE_LENGTH_OF_ARRAY(specs); ++specNdx)
            {
                const std::string name =
                    std::string("") +
                    ((methods[methodNdx].method == ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS) ?
                         ("drawarrays") :
                         ("drawelements")) +
                    ((methods[methodNdx].separateCompute) ? ("_separate") : ("_combined")) + "_grid_" +
                    de::toString(specs[specNdx].gridSize) + "x" + de::toString(specs[specNdx].gridSize) +
                    "_drawcount_" + de::toString(specs[specNdx].numDrawCommands);

                const std::string desc =
                    std::string("Draw grid with ") +
                    ((methods[methodNdx].method == ComputeShaderGeneratedCase::DRAWMETHOD_DRAWARRAYS) ?
                         ("drawarrays indirect") :
                         ("drawelements indirect")) +
                    " calculating buffers in " + ((methods[methodNdx].separateCompute) ? ("separate") : ("combined")) +
                    " compute shader." + " Grid size is " + de::toString(specs[specNdx].gridSize) + "x" +
                    de::toString(specs[specNdx].gridSize) + ", draw count is " +
                    de::toString(specs[specNdx].numDrawCommands);

                const bool computeIndices =
                    (methods[methodNdx].method == ComputeShaderGeneratedCase::DRAWMETHOD_DRAWELEMENTS);

                if (methods[methodNdx].separateCompute)
                    largeGroup->addChild(new ComputeShaderGeneratedSeparateCase(
                        m_context, name.c_str(), desc.c_str(), methods[methodNdx].method, false, true, computeIndices,
                        specs[specNdx].gridSize, specs[specNdx].numDrawCommands));
                else
                    largeGroup->addChild(new ComputeShaderGeneratedCombinedCase(
                        m_context, name.c_str(), desc.c_str(), methods[methodNdx].method, false, true, computeIndices,
                        specs[specNdx].gridSize, specs[specNdx].numDrawCommands));
            }
    }
}

class RandomGroup : public TestCaseGroup
{
public:
    RandomGroup(Context &context, const char *name, const char *descr);
    ~RandomGroup(void);

    void init(void);
};

template <int SIZE>
struct UniformWeightArray
{
    float weights[SIZE];

    UniformWeightArray(void)
    {
        for (int i = 0; i < SIZE; ++i)
            weights[i] = 1.0f;
    }
};

RandomGroup::RandomGroup(Context &context, const char *name, const char *descr) : TestCaseGroup(context, name, descr)
{
}

RandomGroup::~RandomGroup(void)
{
}

void RandomGroup::init(void)
{
    const int numAttempts = 100;

    const int attribCounts[]            = {1, 2, 5};
    const float attribWeights[]         = {30, 10, 1};
    const int primitiveCounts[]         = {1, 5, 64};
    const float primitiveCountWeights[] = {20, 10, 1};
    const int indexOffsets[]            = {0, 7, 13};
    const float indexOffsetWeights[]    = {20, 20, 1};
    const int firsts[]                  = {0, 7, 13};
    const float firstWeights[]          = {20, 20, 1};

    const int instanceCounts[]           = {1, 2, 16, 17};
    const float instanceWeights[]        = {20, 10, 5, 1};
    const int indexMins[]                = {0, 1, 3, 8};
    const int indexMaxs[]                = {4, 8, 128, 257};
    const float indexWeights[]           = {50, 50, 50, 50};
    const int offsets[]                  = {0, 1, 5, 12};
    const float offsetWeights[]          = {50, 10, 10, 10};
    const int strides[]                  = {0, 7, 16, 17};
    const float strideWeights[]          = {50, 10, 10, 10};
    const int instanceDivisors[]         = {0, 1, 3, 129};
    const float instanceDivisorWeights[] = {70, 30, 10, 10};

    const int indirectOffsets[]         = {0, 1, 2};
    const float indirectOffsetWeigths[] = {2, 1, 1};
    const int baseVertices[]            = {0, 1, -2, 4, 3};
    const float baseVertexWeigths[]     = {4, 1, 1, 1, 1};

    gls::DrawTestSpec::Primitive primitives[] = {
        gls::DrawTestSpec::PRIMITIVE_POINTS,       gls::DrawTestSpec::PRIMITIVE_TRIANGLES,
        gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN, gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP,
        gls::DrawTestSpec::PRIMITIVE_LINES,        gls::DrawTestSpec::PRIMITIVE_LINE_STRIP,
        gls::DrawTestSpec::PRIMITIVE_LINE_LOOP};
    const UniformWeightArray<DE_LENGTH_OF_ARRAY(primitives)> primitiveWeights;

    gls::DrawTestSpec::DrawMethod drawMethods[] = {
        gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT,
        gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT,
    };
    const UniformWeightArray<DE_LENGTH_OF_ARRAY(drawMethods)> drawMethodWeights;

    gls::DrawTestSpec::IndexType indexTypes[] = {
        gls::DrawTestSpec::INDEXTYPE_BYTE,
        gls::DrawTestSpec::INDEXTYPE_SHORT,
        gls::DrawTestSpec::INDEXTYPE_INT,
    };
    const UniformWeightArray<DE_LENGTH_OF_ARRAY(indexTypes)> indexTypeWeights;

    gls::DrawTestSpec::InputType inputTypes[] = {
        gls::DrawTestSpec::INPUTTYPE_FLOAT,
        gls::DrawTestSpec::INPUTTYPE_FIXED,
        gls::DrawTestSpec::INPUTTYPE_BYTE,
        gls::DrawTestSpec::INPUTTYPE_SHORT,
        gls::DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE,
        gls::DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT,
        gls::DrawTestSpec::INPUTTYPE_INT,
        gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT,
        gls::DrawTestSpec::INPUTTYPE_HALF,
        gls::DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10,
        gls::DrawTestSpec::INPUTTYPE_INT_2_10_10_10,
    };
    const UniformWeightArray<DE_LENGTH_OF_ARRAY(inputTypes)> inputTypeWeights;

    gls::DrawTestSpec::OutputType outputTypes[] = {
        gls::DrawTestSpec::OUTPUTTYPE_FLOAT, gls::DrawTestSpec::OUTPUTTYPE_VEC2,  gls::DrawTestSpec::OUTPUTTYPE_VEC3,
        gls::DrawTestSpec::OUTPUTTYPE_VEC4,  gls::DrawTestSpec::OUTPUTTYPE_INT,   gls::DrawTestSpec::OUTPUTTYPE_UINT,
        gls::DrawTestSpec::OUTPUTTYPE_IVEC2, gls::DrawTestSpec::OUTPUTTYPE_IVEC3, gls::DrawTestSpec::OUTPUTTYPE_IVEC4,
        gls::DrawTestSpec::OUTPUTTYPE_UVEC2, gls::DrawTestSpec::OUTPUTTYPE_UVEC3, gls::DrawTestSpec::OUTPUTTYPE_UVEC4,
    };
    const UniformWeightArray<DE_LENGTH_OF_ARRAY(outputTypes)> outputTypeWeights;

    gls::DrawTestSpec::Usage usages[] = {
        gls::DrawTestSpec::USAGE_DYNAMIC_DRAW, gls::DrawTestSpec::USAGE_STATIC_DRAW,
        gls::DrawTestSpec::USAGE_STREAM_DRAW,  gls::DrawTestSpec::USAGE_STREAM_READ,
        gls::DrawTestSpec::USAGE_STREAM_COPY,  gls::DrawTestSpec::USAGE_STATIC_READ,
        gls::DrawTestSpec::USAGE_STATIC_COPY,  gls::DrawTestSpec::USAGE_DYNAMIC_READ,
        gls::DrawTestSpec::USAGE_DYNAMIC_COPY,
    };
    const UniformWeightArray<DE_LENGTH_OF_ARRAY(usages)> usageWeights;

    std::set<uint32_t> insertedHashes;
    size_t insertedCount         = 0;
    glu::ContextType contextType = m_context.getRenderContext().getType();
    glu::ApiType apiType         = glu::isContextTypeES(contextType) ? glu::ApiType::es(3, 1) : contextType.getAPI();

    for (int ndx = 0; ndx < numAttempts; ++ndx)
    {
        de::Random random(0xc551393 + ndx); // random does not depend on previous cases

        int attributeCount = random.chooseWeighted<int, const int *, const float *>(
            DE_ARRAY_BEGIN(attribCounts), DE_ARRAY_END(attribCounts), attribWeights);
        int drawCommandSize;
        gls::DrawTestSpec spec;

        spec.apiType   = apiType;
        spec.primitive = random.chooseWeighted<gls::DrawTestSpec::Primitive>(
            DE_ARRAY_BEGIN(primitives), DE_ARRAY_END(primitives), primitiveWeights.weights);
        spec.primitiveCount = random.chooseWeighted<int, const int *, const float *>(
            DE_ARRAY_BEGIN(primitiveCounts), DE_ARRAY_END(primitiveCounts), primitiveCountWeights);
        spec.drawMethod = random.chooseWeighted<gls::DrawTestSpec::DrawMethod>(
            DE_ARRAY_BEGIN(drawMethods), DE_ARRAY_END(drawMethods), drawMethodWeights.weights);

        if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT)
            drawCommandSize = sizeof(uint32_t[4]);
        else if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT)
            drawCommandSize = sizeof(uint32_t[5]);
        else
        {
            DE_ASSERT(false);
            return;
        }

        spec.indexType = random.chooseWeighted<gls::DrawTestSpec::IndexType>(
            DE_ARRAY_BEGIN(indexTypes), DE_ARRAY_END(indexTypes), indexTypeWeights.weights);
        spec.indexPointerOffset = random.chooseWeighted<int, const int *, const float *>(
            DE_ARRAY_BEGIN(indexOffsets), DE_ARRAY_END(indexOffsets), indexOffsetWeights);
        spec.indexStorage  = gls::DrawTestSpec::STORAGE_BUFFER;
        spec.first         = random.chooseWeighted<int, const int *, const float *>(DE_ARRAY_BEGIN(firsts),
                                                                            DE_ARRAY_END(firsts), firstWeights);
        spec.indexMin      = random.chooseWeighted<int, const int *, const float *>(DE_ARRAY_BEGIN(indexMins),
                                                                               DE_ARRAY_END(indexMins), indexWeights);
        spec.indexMax      = random.chooseWeighted<int, const int *, const float *>(DE_ARRAY_BEGIN(indexMaxs),
                                                                               DE_ARRAY_END(indexMaxs), indexWeights);
        spec.instanceCount = random.chooseWeighted<int, const int *, const float *>(
            DE_ARRAY_BEGIN(instanceCounts), DE_ARRAY_END(instanceCounts), instanceWeights);
        spec.indirectOffset = random.chooseWeighted<int, const int *, const float *>(DE_ARRAY_BEGIN(indirectOffsets),
                                                                                     DE_ARRAY_END(indirectOffsets),
                                                                                     indirectOffsetWeigths) *
                              drawCommandSize;
        spec.baseVertex = random.chooseWeighted<int, const int *, const float *>(
            DE_ARRAY_BEGIN(baseVertices), DE_ARRAY_END(baseVertices), baseVertexWeigths);

        // check spec is legal
        if (!spec.valid())
            continue;

        for (int attrNdx = 0; attrNdx < attributeCount;)
        {
            bool valid;
            gls::DrawTestSpec::AttributeSpec attribSpec;

            attribSpec.inputType = random.chooseWeighted<gls::DrawTestSpec::InputType>(
                DE_ARRAY_BEGIN(inputTypes), DE_ARRAY_END(inputTypes), inputTypeWeights.weights);
            attribSpec.outputType = random.chooseWeighted<gls::DrawTestSpec::OutputType>(
                DE_ARRAY_BEGIN(outputTypes), DE_ARRAY_END(outputTypes), outputTypeWeights.weights);
            attribSpec.storage = gls::DrawTestSpec::STORAGE_BUFFER;
            attribSpec.usage   = random.chooseWeighted<gls::DrawTestSpec::Usage>(
                DE_ARRAY_BEGIN(usages), DE_ARRAY_END(usages), usageWeights.weights);
            attribSpec.componentCount = random.getInt(1, 4);
            attribSpec.offset         = random.chooseWeighted<int, const int *, const float *>(
                DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), offsetWeights);
            attribSpec.stride = random.chooseWeighted<int, const int *, const float *>(
                DE_ARRAY_BEGIN(strides), DE_ARRAY_END(strides), strideWeights);
            attribSpec.normalize       = random.getBool();
            attribSpec.instanceDivisor = random.chooseWeighted<int, const int *, const float *>(
                DE_ARRAY_BEGIN(instanceDivisors), DE_ARRAY_END(instanceDivisors), instanceDivisorWeights);
            attribSpec.useDefaultAttribute = random.getBool();

            // check spec is legal
            valid = attribSpec.valid(spec.apiType);

            // we do not want interleaved elements. (Might result in some weird floating point values)
            if (attribSpec.stride &&
                attribSpec.componentCount * gls::DrawTestSpec::inputTypeSize(attribSpec.inputType) > attribSpec.stride)
                valid = false;

            // try again if not valid
            if (valid)
            {
                spec.attribs.push_back(attribSpec);
                ++attrNdx;
            }
        }

        // Do not collapse all vertex positions to a single positions
        if (spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
            spec.attribs[0].instanceDivisor = 0;

        // Is render result meaningful?
        {
            // Only one vertex
            if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED &&
                spec.indexMin == spec.indexMax && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
                continue;
            if (spec.attribs[0].useDefaultAttribute && spec.primitive != gls::DrawTestSpec::PRIMITIVE_POINTS)
                continue;

            // Triangle only on one axis
            if (spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLES ||
                spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN ||
                spec.primitive == gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP)
            {
                if (spec.attribs[0].componentCount == 1)
                    continue;
                if (spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_FLOAT ||
                    spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_INT ||
                    spec.attribs[0].outputType == gls::DrawTestSpec::OUTPUTTYPE_UINT)
                    continue;
                if (spec.drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED &&
                    (spec.indexMax - spec.indexMin) < 2)
                    continue;
            }
        }

        // Add case
        {
            uint32_t hash = spec.hash();
            for (int attrNdx = 0; attrNdx < attributeCount; ++attrNdx)
                hash = (hash << 2) ^ (uint32_t)spec.attribs[attrNdx].hash();

            if (insertedHashes.find(hash) == insertedHashes.end())
            {
                // Only aligned cases
                if (spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET &&
                    spec.isCompatibilityTest() != gls::DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
                    this->addChild(new gls::DrawTest(m_testCtx, m_context.getRenderContext(), spec,
                                                     de::toString(insertedCount).c_str(), spec.getDesc().c_str()));
                insertedHashes.insert(hash);

                ++insertedCount;
            }
        }
    }
}

class BadCommandBufferCase : public TestCase
{
public:
    enum
    {
        CommandSize = 20
    };

    BadCommandBufferCase(Context &context, const char *name, const char *desc, uint32_t alignment, uint32_t bufferSize,
                         bool writeCommandToBuffer, uint32_t m_expectedError);
    ~BadCommandBufferCase(void);

    IterateResult iterate(void);

private:
    const uint32_t m_alignment;
    const uint32_t m_bufferSize;
    const bool m_writeCommandToBuffer;
    const uint32_t m_expectedError;
};

BadCommandBufferCase::BadCommandBufferCase(Context &context, const char *name, const char *desc, uint32_t alignment,
                                           uint32_t bufferSize, bool writeCommandToBuffer, uint32_t expectedError)
    : TestCase(context, name, desc)
    , m_alignment(alignment)
    , m_bufferSize(bufferSize)
    , m_writeCommandToBuffer(writeCommandToBuffer)
    , m_expectedError(expectedError)
{
}

BadCommandBufferCase::~BadCommandBufferCase(void)
{
}

BadCommandBufferCase::IterateResult BadCommandBufferCase::iterate(void)
{
    const tcu::Vec4 vertexPositions[] = {
        tcu::Vec4(0, 0, 0, 1),
        tcu::Vec4(1, 0, 0, 1),
        tcu::Vec4(0, 1, 0, 1),
    };

    const uint16_t indices[] = {
        0,
        2,
        1,
    };

    DE_STATIC_ASSERT(CommandSize == sizeof(DrawElementsCommand));

    sglr::GLContext gl(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS,
                       tcu::IVec4(0, 0, 1, 1));

    uint32_t vaoID           = 0;
    uint32_t positionBuf     = 0;
    uint32_t indexBuf        = 0;
    uint32_t drawIndirectBuf = 0;
    uint32_t error;

    glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources()
                                                                 << glu::VertexSource(s_commonVertexShaderSource)
                                                                 << glu::FragmentSource(s_commonFragmentShaderSource));
    uint32_t programID  = program.getProgram();
    int32_t posLocation = gl.getAttribLocation(programID, "a_position");

    DrawElementsCommand drawCommand;
    drawCommand.count              = 3;
    drawCommand.primCount          = 1;
    drawCommand.firstIndex         = 0;
    drawCommand.baseVertex         = 0;
    drawCommand.reservedMustBeZero = 0;

    std::vector<int8_t> drawCommandBuffer;
    drawCommandBuffer.resize(m_bufferSize);

    deMemset(&drawCommandBuffer[0], 0, (int)drawCommandBuffer.size());

    if (m_writeCommandToBuffer)
    {
        DE_ASSERT(drawCommandBuffer.size() >= sizeof(drawCommand) + m_alignment);
        deMemcpy(&drawCommandBuffer[m_alignment], &drawCommand, sizeof(drawCommand));
    }

    glu::checkError(gl.getError(), "", __FILE__, __LINE__);
    gl.genVertexArrays(1, &vaoID);
    gl.bindVertexArray(vaoID);

    gl.genBuffers(1, &positionBuf);
    gl.bindBuffer(GL_ARRAY_BUFFER, positionBuf);
    gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.vertexAttribDivisor(posLocation, 0);
    gl.enableVertexAttribArray(posLocation);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    gl.genBuffers(1, &indexBuf);
    gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf);
    gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    gl.genBuffers(1, &drawIndirectBuf);
    gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, drawIndirectBuf);
    gl.bufferData(GL_DRAW_INDIRECT_BUFFER, drawCommandBuffer.size(), &drawCommandBuffer[0], GL_STATIC_DRAW);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    gl.viewport(0, 0, 1, 1);

    gl.useProgram(programID);
    gl.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, (const void *)(uintptr_t)m_alignment);

    error = gl.getError();

    gl.useProgram(0);

    gl.deleteBuffers(1, &drawIndirectBuf);
    gl.deleteBuffers(1, &indexBuf);
    gl.deleteBuffers(1, &positionBuf);
    gl.deleteVertexArrays(1, &vaoID);

    m_testCtx.getLog() << tcu::TestLog::Message << "drawElementsIndirect generated " << glu::getErrorStr(error)
                       << ", expecting " << glu::getErrorStr(m_expectedError) << "." << tcu::TestLog::EndMessage;

    if (error == m_expectedError)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tUnexpected error." << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error.");
    }

    return STOP;
}

class BadAlignmentCase : public BadCommandBufferCase
{
public:
    BadAlignmentCase(Context &context, const char *name, const char *desc, uint32_t alignment);
    ~BadAlignmentCase(void);
};

BadAlignmentCase::BadAlignmentCase(Context &context, const char *name, const char *desc, uint32_t alignment)
    : BadCommandBufferCase(context, name, desc, alignment, CommandSize + alignment, true, GL_INVALID_VALUE)
{
}

BadAlignmentCase::~BadAlignmentCase(void)
{
}

class BadBufferRangeCase : public BadCommandBufferCase
{
public:
    BadBufferRangeCase(Context &context, const char *name, const char *desc, uint32_t offset);
    ~BadBufferRangeCase(void);
};

BadBufferRangeCase::BadBufferRangeCase(Context &context, const char *name, const char *desc, uint32_t offset)
    : BadCommandBufferCase(context, name, desc, offset, CommandSize, false, GL_INVALID_OPERATION)
{
}

BadBufferRangeCase::~BadBufferRangeCase(void)
{
}

class BadStateCase : public TestCase
{
public:
    enum CaseType
    {
        CASE_CLIENT_BUFFER_VERTEXATTR = 0,
        CASE_CLIENT_BUFFER_COMMAND,
        CASE_DEFAULT_VAO,

        CASE_CLIENT_LAST
    };

    BadStateCase(Context &context, const char *name, const char *desc, CaseType type);
    ~BadStateCase(void);

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

private:
    const CaseType m_caseType;
};

BadStateCase::BadStateCase(Context &context, const char *name, const char *desc, CaseType type)
    : TestCase(context, name, desc)
    , m_caseType(type)
{
    DE_ASSERT(type < CASE_CLIENT_LAST);
}

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

void BadStateCase::init(void)
{
    if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
    {
        if (m_caseType == CASE_CLIENT_BUFFER_VERTEXATTR)
            throw tcu::NotSupportedError(
                "The negative test for vertex attrib array in the client memory is not supported in the GL context");
        if (m_caseType == CASE_DEFAULT_VAO)
            throw tcu::NotSupportedError(
                "The negative test for use with default vao is not supported in the GL context");
    }
}

void BadStateCase::deinit(void)
{
}

BadStateCase::IterateResult BadStateCase::iterate(void)
{
    const tcu::Vec4 vertexPositions[] = {
        tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),
        tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
        tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
    };

    const uint16_t indices[] = {
        0,
        2,
        1,
    };

    sglr::GLContext gl(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS,
                       tcu::IVec4(0, 0, 1, 1));

    uint32_t error;
    glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources()
                                                                 << glu::VertexSource(s_commonVertexShaderSource)
                                                                 << glu::FragmentSource(s_commonFragmentShaderSource));
    uint32_t vaoID         = 0;
    uint32_t dataBufferID  = 0;
    uint32_t indexBufferID = 0;
    uint32_t cmdBufferID   = 0;

    const uint32_t programID  = program.getProgram();
    const int32_t posLocation = gl.getAttribLocation(programID, "a_position");

    DrawElementsCommand drawCommand;
    drawCommand.count              = 3;
    drawCommand.primCount          = 1;
    drawCommand.firstIndex         = 0;
    drawCommand.baseVertex         = 0;
    drawCommand.reservedMustBeZero = 0;

    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    if (m_caseType == CASE_CLIENT_BUFFER_VERTEXATTR)
    {
        // \note We use default VAO since we use client pointers. Trying indirect draw with default VAO is also an error. => This test does two illegal operations

        gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, vertexPositions);
        gl.enableVertexAttribArray(posLocation);
        glu::checkError(gl.getError(), "", __FILE__, __LINE__);
    }
    else if (m_caseType == CASE_CLIENT_BUFFER_COMMAND)
    {
        gl.genVertexArrays(1, &vaoID);
        gl.bindVertexArray(vaoID);

        gl.genBuffers(1, &dataBufferID);
        gl.bindBuffer(GL_ARRAY_BUFFER, dataBufferID);
        gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
        gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
        gl.enableVertexAttribArray(posLocation);
        glu::checkError(gl.getError(), "", __FILE__, __LINE__);
    }
    else if (m_caseType == CASE_DEFAULT_VAO)
    {
        gl.genBuffers(1, &dataBufferID);
        gl.bindBuffer(GL_ARRAY_BUFFER, dataBufferID);
        gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
        gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
        gl.enableVertexAttribArray(posLocation);
        glu::checkError(gl.getError(), "", __FILE__, __LINE__);
    }
    else
        DE_ASSERT(false);

    gl.genBuffers(1, &indexBufferID);
    gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
    gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    if (m_caseType != CASE_CLIENT_BUFFER_COMMAND)
    {
        gl.genBuffers(1, &cmdBufferID);
        gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, cmdBufferID);
        gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
        glu::checkError(gl.getError(), "", __FILE__, __LINE__);
    }

    gl.viewport(0, 0, 1, 1);

    gl.useProgram(programID);
    gl.drawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT,
                            (m_caseType != CASE_CLIENT_BUFFER_COMMAND) ? (DE_NULL) : (&drawCommand));

    error = gl.getError();

    gl.bindVertexArray(0);
    gl.useProgram(0);

    if (error == GL_INVALID_OPERATION)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Unexpected error. Expected GL_INVALID_OPERATION, got "
                           << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error.");
    }

    return STOP;
}

class BadDrawModeCase : public TestCase
{
public:
    enum DrawType
    {
        DRAW_ARRAYS = 0,
        DRAW_ELEMENTS,
        DRAW_ELEMENTS_BAD_INDEX,

        DRAW_LAST
    };

    BadDrawModeCase(Context &context, const char *name, const char *desc, DrawType type);
    ~BadDrawModeCase(void);

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

private:
    const DrawType m_drawType;
};

BadDrawModeCase::BadDrawModeCase(Context &context, const char *name, const char *desc, DrawType type)
    : TestCase(context, name, desc)
    , m_drawType(type)
{
    DE_ASSERT(type < DRAW_LAST);
}

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

void BadDrawModeCase::init(void)
{
}

void BadDrawModeCase::deinit(void)
{
}

BadDrawModeCase::IterateResult BadDrawModeCase::iterate(void)
{
    const tcu::Vec4 vertexPositions[] = {
        tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f),
        tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f),
        tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f),
    };

    const uint16_t indices[] = {
        0,
        2,
        1,
    };

    sglr::GLContext gl(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS,
                       tcu::IVec4(0, 0, 1, 1));

    uint32_t error;
    glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources()
                                                                 << glu::VertexSource(s_commonVertexShaderSource)
                                                                 << glu::FragmentSource(s_commonFragmentShaderSource));
    uint32_t vaoID         = 0;
    uint32_t dataBufferID  = 0;
    uint32_t indexBufferID = 0;
    uint32_t cmdBufferID   = 0;

    const uint32_t programID    = program.getProgram();
    const int32_t posLocation   = gl.getAttribLocation(programID, "a_position");
    const glw::GLenum mode      = (m_drawType == DRAW_ELEMENTS_BAD_INDEX) ? (GL_TRIANGLES) : (0x123);
    const glw::GLenum indexType = (m_drawType == DRAW_ELEMENTS_BAD_INDEX) ? (0x123) : (GL_UNSIGNED_SHORT);

    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    // vao

    gl.genVertexArrays(1, &vaoID);
    gl.bindVertexArray(vaoID);

    // va

    gl.genBuffers(1, &dataBufferID);
    gl.bindBuffer(GL_ARRAY_BUFFER, dataBufferID);
    gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(posLocation);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    // index

    if (m_drawType == DRAW_ELEMENTS || m_drawType == DRAW_ELEMENTS_BAD_INDEX)
    {
        gl.genBuffers(1, &indexBufferID);
        gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
        gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
        glu::checkError(gl.getError(), "", __FILE__, __LINE__);
    }

    // cmd

    gl.genBuffers(1, &cmdBufferID);
    gl.bindBuffer(GL_DRAW_INDIRECT_BUFFER, cmdBufferID);
    if (m_drawType == DRAW_ELEMENTS || m_drawType == DRAW_ELEMENTS_BAD_INDEX)
    {
        DrawElementsCommand drawCommand;
        drawCommand.count              = 3;
        drawCommand.primCount          = 1;
        drawCommand.firstIndex         = 0;
        drawCommand.baseVertex         = 0;
        drawCommand.reservedMustBeZero = 0;

        gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
    }
    else if (m_drawType == DRAW_ARRAYS)
    {
        DrawArraysCommand drawCommand;
        drawCommand.count              = 3;
        drawCommand.primCount          = 1;
        drawCommand.first              = 0;
        drawCommand.reservedMustBeZero = 0;

        gl.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(drawCommand), &drawCommand, GL_STATIC_DRAW);
    }
    else
        DE_ASSERT(false);
    glu::checkError(gl.getError(), "", __FILE__, __LINE__);

    gl.viewport(0, 0, 1, 1);
    gl.useProgram(programID);
    if (m_drawType == DRAW_ELEMENTS || m_drawType == DRAW_ELEMENTS_BAD_INDEX)
        gl.drawElementsIndirect(mode, indexType, DE_NULL);
    else if (m_drawType == DRAW_ARRAYS)
        gl.drawArraysIndirect(mode, DE_NULL);
    else
        DE_ASSERT(false);

    error = gl.getError();
    gl.useProgram(0);

    if (error == GL_INVALID_ENUM)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Unexpected error. Expected GL_INVALID_ENUM, got "
                           << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected error.");
    }

    return STOP;
}

class NegativeGroup : public TestCaseGroup
{
public:
    NegativeGroup(Context &context, const char *name, const char *descr);
    ~NegativeGroup(void);

    void init(void);
};

NegativeGroup::NegativeGroup(Context &context, const char *name, const char *descr)
    : TestCaseGroup(context, name, descr)
{
}

NegativeGroup::~NegativeGroup(void)
{
}

void NegativeGroup::init(void)
{
    // invalid alignment
    addChild(new BadAlignmentCase(m_context, "command_bad_alignment_1", "Bad command alignment", 1));
    addChild(new BadAlignmentCase(m_context, "command_bad_alignment_2", "Bad command alignment", 2));
    addChild(new BadAlignmentCase(m_context, "command_bad_alignment_3", "Bad command alignment", 3));

    // command only partially or not at all in the buffer
    addChild(new BadBufferRangeCase(m_context, "command_offset_partially_in_buffer",
                                    "Command not fully in the buffer range", BadBufferRangeCase::CommandSize - 16));
    addChild(new BadBufferRangeCase(m_context, "command_offset_not_in_buffer", "Command not in the buffer range",
                                    BadBufferRangeCase::CommandSize));
    addChild(new BadBufferRangeCase(m_context, "command_offset_not_in_buffer_unsigned32_wrap",
                                    "Command not in the buffer range", 0xFFFFFFFC));
    addChild(new BadBufferRangeCase(m_context, "command_offset_not_in_buffer_signed32_wrap",
                                    "Command not in the buffer range", 0x7FFFFFFC));

    // use with client data and default vao
    addChild(new BadStateCase(m_context, "client_vertex_attrib_array", "Vertex attrib array in the client memory",
                              BadStateCase::CASE_CLIENT_BUFFER_VERTEXATTR));
    addChild(new BadStateCase(m_context, "client_command_array", "Command array in the client memory",
                              BadStateCase::CASE_CLIENT_BUFFER_COMMAND));
    addChild(new BadStateCase(m_context, "default_vao", "Use with default vao", BadStateCase::CASE_DEFAULT_VAO));

    // invalid mode & type
    addChild(new BadDrawModeCase(m_context, "invalid_mode_draw_arrays", "Call DrawArraysIndirect with bad mode",
                                 BadDrawModeCase::DRAW_ARRAYS));
    addChild(new BadDrawModeCase(m_context, "invalid_mode_draw_elements", "Call DrawelementsIndirect with bad mode",
                                 BadDrawModeCase::DRAW_ELEMENTS));
    addChild(new BadDrawModeCase(m_context, "invalid_type_draw_elements", "Call DrawelementsIndirect with bad type",
                                 BadDrawModeCase::DRAW_ELEMENTS_BAD_INDEX));
}

} // namespace

DrawTests::DrawTests(Context &context) : TestCaseGroup(context, "draw_indirect", "Indirect drawing tests")
{
}

DrawTests::~DrawTests(void)
{
}

void DrawTests::init(void)
{
    // Basic
    {
        const gls::DrawTestSpec::DrawMethod basicMethods[] = {
            gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT,
            gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT,
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(basicMethods); ++ndx)
        {
            const std::string name = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);
            const std::string desc = gls::DrawTestSpec::drawMethodToString(basicMethods[ndx]);

            this->addChild(new MethodGroup(m_context, name.c_str(), desc.c_str(), basicMethods[ndx]));
        }
    }

    // extreme instancing

    this->addChild(new InstancingGroup(m_context, "instancing", "draw tests with a large instance count."));

    // compute shader generated commands

    this->addChild(new ComputeShaderGeneratedGroup(m_context, "compute_interop",
                                                   "draw tests with a draw command generated in compute shader."));

    // Random

    this->addChild(new RandomGroup(m_context, "random", "random draw commands."));

    // negative

    this->addChild(new NegativeGroup(m_context, "negative", "invalid draw commands with defined error codes."));
}

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