/*-------------------------------------------------------------------------
 * 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 Basic Compute Shader Tests.
 *//*--------------------------------------------------------------------*/

#include "es31fAtomicCounterTests.hpp"

#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluRenderContext.hpp"

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

#include "tcuTestLog.hpp"

#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include "deMemory.h"

#include <vector>
#include <string>

using namespace glw;
using tcu::TestLog;

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

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

class AtomicCounterTest : public TestCase
{
public:
    enum Operation
    {
        OPERATION_INC = (1 << 0),
        OPERATION_DEC = (1 << 1),
        OPERATION_GET = (1 << 2)
    };

    enum OffsetType
    {
        OFFSETTYPE_NONE = 0,
        OFFSETTYPE_BASIC,
        OFFSETTYPE_REVERSE,
        OFFSETTYPE_FIRST_AUTO,
        OFFSETTYPE_DEFAULT_AUTO,
        OFFSETTYPE_RESET_DEFAULT,
        OFFSETTYPE_INVALID,
        OFFSETTYPE_INVALID_OVERLAPPING,
        OFFSETTYPE_INVALID_DEFAULT
    };

    enum BindingType
    {
        BINDINGTYPE_BASIC = 0,
        BINDINGTYPE_INVALID,
        BINDINGTYPE_INVALID_DEFAULT
    };

    struct TestSpec
    {
        TestSpec(void)
            : atomicCounterCount(0)
            , operations((Operation)0)
            , callCount(0)
            , useBranches(false)
            , threadCount(0)
            , offsetType(OFFSETTYPE_NONE)
            , bindingType(BINDINGTYPE_BASIC)
        {
        }

        int atomicCounterCount;
        Operation operations;
        int callCount;
        bool useBranches;
        int threadCount;
        OffsetType offsetType;
        BindingType bindingType;
    };

    AtomicCounterTest(Context &context, const char *name, const char *description, const TestSpec &spec);
    ~AtomicCounterTest(void);

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

private:
    const TestSpec m_spec;

    bool checkAndLogCounterValues(TestLog &log, const vector<uint32_t> &counters) const;
    bool checkAndLogCallValues(TestLog &log, const vector<uint32_t> &increments, const vector<uint32_t> &decrements,
                               const vector<uint32_t> &preGets, const vector<uint32_t> &postGets,
                               const vector<uint32_t> &gets) const;
    void splitBuffer(const vector<uint32_t> &buffer, vector<uint32_t> &increments, vector<uint32_t> &decrements,
                     vector<uint32_t> &preGets, vector<uint32_t> &postGets, vector<uint32_t> &gets) const;
    uint32_t getInitialValue(void) const
    {
        return m_spec.callCount * m_spec.threadCount + 1;
    }

    static string generateShaderSource(const TestSpec &spec);
    static void getCountersValues(vector<uint32_t> &counterValues, const vector<uint32_t> &values, int ndx,
                                  int counterCount);
    static bool checkRange(TestLog &log, const vector<uint32_t> &values, const vector<uint32_t> &min,
                           const vector<uint32_t> &max);
    static bool checkUniquenessAndLinearity(TestLog &log, const vector<uint32_t> &values);
    static bool checkPath(const vector<uint32_t> &increments, const vector<uint32_t> &decrements, int initialValue,
                          const TestSpec &spec);

    int getOperationCount(void) const;

    AtomicCounterTest &operator=(const AtomicCounterTest &);
    AtomicCounterTest(const AtomicCounterTest &);
};

int AtomicCounterTest::getOperationCount(void) const
{
    int count = 0;

    if (m_spec.operations & OPERATION_INC)
        count++;

    if (m_spec.operations & OPERATION_DEC)
        count++;

    if (m_spec.operations == OPERATION_GET)
        count++;
    else if (m_spec.operations & OPERATION_GET)
        count += 2;

    return count;
}

AtomicCounterTest::AtomicCounterTest(Context &context, const char *name, const char *description, const TestSpec &spec)
    : TestCase(context, name, description)
    , m_spec(spec)
{
}

AtomicCounterTest::~AtomicCounterTest(void)
{
}

void AtomicCounterTest::init(void)
{
}

void AtomicCounterTest::deinit(void)
{
}

string AtomicCounterTest::generateShaderSource(const TestSpec &spec)
{
    std::ostringstream src;

    src << "#version 310 es\n"
        << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n";

    {
        bool wroteLayout = false;

        switch (spec.bindingType)
        {
        case BINDINGTYPE_INVALID_DEFAULT:
            src << "layout(binding=10000";
            wroteLayout = true;
            break;

        default:
            // Do nothing
            break;
        }

        switch (spec.offsetType)
        {
        case OFFSETTYPE_DEFAULT_AUTO:
            if (!wroteLayout)
                src << "layout(binding=0, ";
            else
                src << ", ";

            src << "offset=4";
            wroteLayout = true;
            break;

        case OFFSETTYPE_RESET_DEFAULT:
            DE_ASSERT(spec.atomicCounterCount > 2);

            if (!wroteLayout)
                src << "layout(binding=0, ";
            else
                src << ", ";

            src << "offset=" << (4 * spec.atomicCounterCount / 2);
            wroteLayout = true;
            break;

        case OFFSETTYPE_INVALID_DEFAULT:
            if (!wroteLayout)
                src << "layout(binding=0, ";
            else
                src << ", ";

            src << "offset=1";
            wroteLayout = true;
            break;

        default:
            // Do nothing
            break;
        }

        if (wroteLayout)
            src << ") uniform atomic_uint;\n";
    }

    src << "layout(binding = 1, std430) buffer Output {\n";

    if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
        src << "    uint preGet[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";

    if ((spec.operations & OPERATION_INC) != 0)
        src << "    uint increment[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";

    if ((spec.operations & OPERATION_DEC) != 0)
        src << "    uint decrement[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";

    if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
        src << "    uint postGet[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";

    if (spec.operations == OPERATION_GET)
        src << "    uint get[" << spec.threadCount * spec.atomicCounterCount * spec.callCount << "];\n";

    src << "} sb_in;\n\n";

    for (int counterNdx = 0; counterNdx < spec.atomicCounterCount; counterNdx++)
    {
        bool layoutStarted = false;

        if (spec.offsetType == OFFSETTYPE_RESET_DEFAULT && counterNdx == spec.atomicCounterCount / 2)
            src << "layout(binding=0, offset=0) uniform atomic_uint;\n";

        switch (spec.bindingType)
        {
        case BINDINGTYPE_BASIC:
            layoutStarted = true;
            src << "layout(binding=0";
            break;

        case BINDINGTYPE_INVALID:
            layoutStarted = true;
            src << "layout(binding=10000";
            break;

        case BINDINGTYPE_INVALID_DEFAULT:
            // Nothing
            break;

        default:
            DE_ASSERT(false);
        }

        switch (spec.offsetType)
        {
        case OFFSETTYPE_NONE:
            if (layoutStarted)
                src << ") ";

            src << "uniform atomic_uint counter" << counterNdx << ";\n";

            break;

        case OFFSETTYPE_BASIC:
            if (!layoutStarted)
                src << "layout(";
            else
                src << ", ";

            src << "offset=" << (counterNdx * 4) << ") uniform atomic_uint counter" << counterNdx << ";\n";

            break;

        case OFFSETTYPE_INVALID_DEFAULT:
            if (layoutStarted)
                src << ") ";

            src << "uniform atomic_uint counter" << counterNdx << ";\n";

            break;

        case OFFSETTYPE_INVALID:
            if (!layoutStarted)
                src << "layout(";
            else
                src << ", ";

            src << "offset=" << (1 + counterNdx * 2) << ") uniform atomic_uint counter" << counterNdx << ";\n";

            break;

        case OFFSETTYPE_INVALID_OVERLAPPING:
            if (!layoutStarted)
                src << "layout(";
            else
                src << ", ";

            src << "offset=0) uniform atomic_uint counter" << counterNdx << ";\n";

            break;

        case OFFSETTYPE_REVERSE:
            if (!layoutStarted)
                src << "layout(";
            else
                src << ", ";

            src << "offset=" << (spec.atomicCounterCount - counterNdx - 1) * 4 << ") uniform atomic_uint counter"
                << (spec.atomicCounterCount - counterNdx - 1) << ";\n";

            break;

        case OFFSETTYPE_FIRST_AUTO:
            DE_ASSERT(spec.atomicCounterCount > 2);

            if (counterNdx + 1 == spec.atomicCounterCount)
            {
                if (!layoutStarted)
                    src << "layout(";
                else
                    src << ", ";

                src << "offset=0) uniform atomic_uint counter0;\n";
            }
            else if (counterNdx == 0)
            {
                if (!layoutStarted)
                    src << "layout(";
                else
                    src << ", ";

                src << "offset=4) uniform atomic_uint counter1;\n";
            }
            else
            {
                if (layoutStarted)
                    src << ") ";

                src << "uniform atomic_uint counter" << (counterNdx + 1) << ";\n";
            }

            break;

        case OFFSETTYPE_DEFAULT_AUTO:
            if (counterNdx + 1 == spec.atomicCounterCount)
            {
                if (!layoutStarted)
                    src << "layout(";
                else
                    src << ", ";

                src << "offset=0) uniform atomic_uint counter0;\n";
            }
            else
            {
                if (layoutStarted)
                    src << ") ";

                src << "uniform atomic_uint counter" << (counterNdx + 1) << ";\n";
            }

            break;

        case OFFSETTYPE_RESET_DEFAULT:
            if (layoutStarted)
                src << ") ";

            if (counterNdx < spec.atomicCounterCount / 2)
                src << "uniform atomic_uint counter" << (counterNdx + spec.atomicCounterCount / 2) << ";\n";
            else
                src << "uniform atomic_uint counter" << (counterNdx - spec.atomicCounterCount / 2) << ";\n";

            break;

        default:
            DE_ASSERT(false);
        }
    }

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

    if (spec.callCount > 1)
        src << "\tfor (uint i = 0u; i < " << spec.callCount << "u; i++)\n";

    src << "\t{\n"
        << "\t\tuint id = (gl_GlobalInvocationID.x";

    if (spec.callCount > 1)
        src << " * " << spec.callCount << "u";

    if (spec.callCount > 1)
        src << " + i)";
    else
        src << ")";

    if (spec.atomicCounterCount > 1)
        src << " * " << spec.atomicCounterCount << "u";

    src << ";\n";

    for (int counterNdx = 0; counterNdx < spec.atomicCounterCount; counterNdx++)
    {
        if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
            src << "\t\tsb_in.preGet[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n";

        if (spec.useBranches &&
            ((spec.operations & (OPERATION_INC | OPERATION_DEC)) == (OPERATION_INC | OPERATION_DEC)))
        {
            src << "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "") << ") % 2u) == 0u)\n"
                << "\t\t{\n"
                << "\t\t\tsb_in.increment[id + " << counterNdx << "u] = atomicCounterIncrement(counter" << counterNdx
                << ");\n"
                << "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = uint(-1);\n"
                << "\t\t}\n"
                << "\t\telse\n"
                << "\t\t{\n"
                << "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = atomicCounterDecrement(counter" << counterNdx
                << ") + 1u;\n"
                << "\t\t\tsb_in.increment[id + " << counterNdx << "u] = uint(-1);\n"
                << "\t\t}\n";
        }
        else
        {
            if ((spec.operations & OPERATION_INC) != 0)
            {
                if (spec.useBranches)
                {
                    src << "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "")
                        << ") % 2u) == 0u)\n"
                        << "\t\t{\n"
                        << "\t\t\tsb_in.increment[id + " << counterNdx << "u] = atomicCounterIncrement(counter"
                        << counterNdx << ");\n"
                        << "\t\t}\n"
                        << "\t\telse\n"
                        << "\t\t{\n"
                        << "\t\t\tsb_in.increment[id + " << counterNdx << "u] = uint(-1);\n"
                        << "\t\t}\n";
                }
                else
                    src << "\t\tsb_in.increment[id + " << counterNdx << "u] = atomicCounterIncrement(counter"
                        << counterNdx << ");\n";
            }

            if ((spec.operations & OPERATION_DEC) != 0)
            {
                if (spec.useBranches)
                {
                    src << "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "")
                        << ") % 2u) == 0u)\n"
                        << "\t\t{\n"
                        << "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = atomicCounterDecrement(counter"
                        << counterNdx << ") + 1u;\n"
                        << "\t\t}\n"
                        << "\t\telse\n"
                        << "\t\t{\n"
                        << "\t\t\tsb_in.decrement[id + " << counterNdx << "u] = uint(-1);\n"
                        << "\t\t}\n";
                }
                else
                    src << "\t\tsb_in.decrement[id + " << counterNdx << "u] = atomicCounterDecrement(counter"
                        << counterNdx << ") + 1u;\n";
            }
        }

        if ((spec.operations & OPERATION_GET) != 0 && spec.operations != OPERATION_GET)
            src << "\t\tsb_in.postGet[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n";

        if ((spec.operations == OPERATION_GET) != 0)
        {
            if (spec.useBranches)
            {
                src << "\t\tif (((gl_GlobalInvocationID.x" << (spec.callCount > 1 ? " + i" : "") << ") % 2u) == 0u)\n"
                    << "\t\t{\n"
                    << "\t\t\tsb_in.get[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n"
                    << "\t\t}\n"
                    << "\t\telse\n"
                    << "\t\t{\n"
                    << "\t\t\tsb_in.get[id + " << counterNdx << "u] = uint(-1);\n"
                    << "\t\t}\n";
            }
            else
                src << "\t\tsb_in.get[id + " << counterNdx << "u] = atomicCounter(counter" << counterNdx << ");\n";
        }
    }

    src << "\t}\n"
        << "}\n";

    return src.str();
}

bool AtomicCounterTest::checkAndLogCounterValues(TestLog &log, const vector<uint32_t> &counters) const
{
    tcu::ScopedLogSection counterSection(log, "Counter info",
                                         "Show initial value, current value and expected value of each counter.");
    bool isOk = true;

    // Check that atomic counters have sensible results
    for (int counterNdx = 0; counterNdx < (int)counters.size(); counterNdx++)
    {
        const uint32_t value        = counters[counterNdx];
        const uint32_t initialValue = getInitialValue();
        uint32_t expectedValue      = (uint32_t)-1;

        if ((m_spec.operations & OPERATION_INC) != 0 && (m_spec.operations & OPERATION_DEC) == 0)
            expectedValue = initialValue + (m_spec.useBranches ? m_spec.threadCount * m_spec.callCount -
                                                                     m_spec.threadCount * m_spec.callCount / 2 :
                                                                 m_spec.threadCount * m_spec.callCount);

        if ((m_spec.operations & OPERATION_INC) == 0 && (m_spec.operations & OPERATION_DEC) != 0)
            expectedValue = initialValue - (m_spec.useBranches ? m_spec.threadCount * m_spec.callCount -
                                                                     m_spec.threadCount * m_spec.callCount / 2 :
                                                                 m_spec.threadCount * m_spec.callCount);

        if ((m_spec.operations & OPERATION_INC) != 0 && (m_spec.operations & OPERATION_DEC) != 0)
            expectedValue = initialValue +
                            (m_spec.useBranches ?
                                 m_spec.threadCount * m_spec.callCount - m_spec.threadCount * m_spec.callCount / 2 :
                                 0) -
                            (m_spec.useBranches ? m_spec.threadCount * m_spec.callCount / 2 : 0);

        if ((m_spec.operations & OPERATION_INC) == 0 && (m_spec.operations & OPERATION_DEC) == 0)
            expectedValue = initialValue;

        log << TestLog::Message << "atomic_uint counter" << counterNdx << " initial value: " << initialValue
            << ", value: " << value << ", expected: " << expectedValue << (value == expectedValue ? "" : ", failed!")
            << TestLog::EndMessage;

        if (value != expectedValue)
            isOk = false;
    }

    return isOk;
}

void AtomicCounterTest::splitBuffer(const vector<uint32_t> &buffer, vector<uint32_t> &increments,
                                    vector<uint32_t> &decrements, vector<uint32_t> &preGets, vector<uint32_t> &postGets,
                                    vector<uint32_t> &gets) const
{
    const int bufferValueCount = m_spec.callCount * m_spec.threadCount * m_spec.atomicCounterCount;

    int firstPreGet  = -1;
    int firstPostGet = -1;
    int firstGet     = -1;
    int firstInc     = -1;
    int firstDec     = -1;

    increments.clear();
    decrements.clear();
    preGets.clear();
    postGets.clear();
    gets.clear();

    if (m_spec.operations == OPERATION_GET)
        firstGet = 0;
    else if (m_spec.operations == OPERATION_INC)
        firstInc = 0;
    else if (m_spec.operations == OPERATION_DEC)
        firstDec = 0;
    else if (m_spec.operations == (OPERATION_GET | OPERATION_INC))
    {
        firstPreGet  = 0;
        firstInc     = bufferValueCount;
        firstPostGet = bufferValueCount * 2;
    }
    else if (m_spec.operations == (OPERATION_GET | OPERATION_DEC))
    {
        firstPreGet  = 0;
        firstDec     = bufferValueCount;
        firstPostGet = bufferValueCount * 2;
    }
    else if (m_spec.operations == (OPERATION_GET | OPERATION_DEC | OPERATION_INC))
    {
        firstPreGet  = 0;
        firstInc     = bufferValueCount;
        firstDec     = bufferValueCount * 2;
        firstPostGet = bufferValueCount * 3;
    }
    else if (m_spec.operations == (OPERATION_DEC | OPERATION_INC))
    {
        firstInc = 0;
        firstDec = bufferValueCount;
    }
    else
        DE_ASSERT(false);

    for (int threadNdx = 0; threadNdx < m_spec.threadCount; threadNdx++)
    {
        for (int callNdx = 0; callNdx < m_spec.callCount; callNdx++)
        {
            for (int counterNdx = 0; counterNdx < m_spec.atomicCounterCount; counterNdx++)
            {
                const int id = ((threadNdx * m_spec.callCount) + callNdx) * m_spec.atomicCounterCount + counterNdx;

                if (firstInc != -1)
                    increments.push_back(buffer[firstInc + id]);

                if (firstDec != -1)
                    decrements.push_back(buffer[firstDec + id]);

                if (firstPreGet != -1)
                    preGets.push_back(buffer[firstPreGet + id]);

                if (firstPostGet != -1)
                    postGets.push_back(buffer[firstPostGet + id]);

                if (firstGet != -1)
                    gets.push_back(buffer[firstGet + id]);
            }
        }
    }
}

void AtomicCounterTest::getCountersValues(vector<uint32_t> &counterValues, const vector<uint32_t> &values, int ndx,
                                          int counterCount)
{
    counterValues.resize(values.size() / counterCount, 0);

    DE_ASSERT(values.size() % counterCount == 0);

    for (int valueNdx = 0; valueNdx < (int)counterValues.size(); valueNdx++)
        counterValues[valueNdx] = values[valueNdx * counterCount + ndx];
}

bool AtomicCounterTest::checkRange(TestLog &log, const vector<uint32_t> &values, const vector<uint32_t> &min,
                                   const vector<uint32_t> &max)
{
    int failedCount = 0;

    DE_ASSERT(values.size() == min.size());
    DE_ASSERT(values.size() == max.size());

    for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
    {
        if (values[valueNdx] != (uint32_t)-1)
        {
            if (!deInRange32(values[valueNdx], min[valueNdx], max[valueNdx]))
            {
                if (failedCount < 20)
                    log << TestLog::Message << "Value " << values[valueNdx] << " not in range [" << min[valueNdx]
                        << ", " << max[valueNdx] << "]." << TestLog::EndMessage;
                failedCount++;
            }
        }
    }

    if (failedCount > 20)
        log << TestLog::Message << "Number of values not in range: " << failedCount << ", displaying first 20 values."
            << TestLog::EndMessage;

    return failedCount == 0;
}

bool AtomicCounterTest::checkUniquenessAndLinearity(TestLog &log, const vector<uint32_t> &values)
{
    vector<uint32_t> counts;
    int failedCount   = 0;
    uint32_t minValue = (uint32_t)-1;
    uint32_t maxValue = 0;

    DE_ASSERT(!values.empty());

    for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
    {
        if (values[valueNdx] != (uint32_t)-1)
        {
            minValue = std::min(minValue, values[valueNdx]);
            maxValue = std::max(maxValue, values[valueNdx]);
        }
    }

    counts.resize(maxValue - minValue + 1, 0);

    for (int valueNdx = 0; valueNdx < (int)values.size(); valueNdx++)
    {
        if (values[valueNdx] != (uint32_t)-1)
            counts[values[valueNdx] - minValue]++;
    }

    for (int countNdx = 0; countNdx < (int)counts.size(); countNdx++)
    {
        if (counts[countNdx] != 1)
        {
            if (failedCount < 20)
                log << TestLog::Message << "Value " << (minValue + countNdx) << " is not unique. Returned "
                    << counts[countNdx] << " times." << TestLog::EndMessage;

            failedCount++;
        }
    }

    if (failedCount > 20)
        log << TestLog::Message << "Number of values not unique: " << failedCount << ", displaying first 20 values."
            << TestLog::EndMessage;

    return failedCount == 0;
}

bool AtomicCounterTest::checkPath(const vector<uint32_t> &increments, const vector<uint32_t> &decrements,
                                  int initialValue, const TestSpec &spec)
{
    const uint32_t lastValue =
        initialValue +
        (spec.useBranches ? spec.threadCount * spec.callCount - spec.threadCount * spec.callCount / 2 : 0) -
        (spec.useBranches ? spec.threadCount * spec.callCount / 2 : 0);
    bool isOk = true;

    vector<uint32_t> incrementCounts;
    vector<uint32_t> decrementCounts;

    uint32_t minValue = 0xFFFFFFFFu;
    uint32_t maxValue = 0;

    for (int valueNdx = 0; valueNdx < (int)increments.size(); valueNdx++)
    {
        if (increments[valueNdx] != (uint32_t)-1)
        {
            minValue = std::min(minValue, increments[valueNdx]);
            maxValue = std::max(maxValue, increments[valueNdx]);
        }
    }

    for (int valueNdx = 0; valueNdx < (int)decrements.size(); valueNdx++)
    {
        if (decrements[valueNdx] != (uint32_t)-1)
        {
            minValue = std::min(minValue, decrements[valueNdx]);
            maxValue = std::max(maxValue, decrements[valueNdx]);
        }
    }

    minValue = std::min(minValue, (uint32_t)initialValue);
    maxValue = std::max(maxValue, (uint32_t)initialValue);

    incrementCounts.resize(maxValue - minValue + 1, 0);
    decrementCounts.resize(maxValue - minValue + 1, 0);

    for (int valueNdx = 0; valueNdx < (int)increments.size(); valueNdx++)
    {
        if (increments[valueNdx] != (uint32_t)-1)
            incrementCounts[increments[valueNdx] - minValue]++;
    }

    for (int valueNdx = 0; valueNdx < (int)decrements.size(); valueNdx++)
    {
        if (decrements[valueNdx] != (uint32_t)-1)
            decrementCounts[decrements[valueNdx] - minValue]++;
    }

    int pos = initialValue - minValue;

    while (incrementCounts[pos] + decrementCounts[pos] != 0)
    {
        if (incrementCounts[pos] > 0 && pos >= (int)(lastValue - minValue))
        {
            // If can increment and incrementation would move us away from result value, increment
            incrementCounts[pos]--;
            pos++;
        }
        else if (decrementCounts[pos] > 0)
        {
            // If can, decrement
            decrementCounts[pos]--;
            pos--;
        }
        else if (incrementCounts[pos] > 0)
        {
            // If increment moves closer to result value and can't decrement, increment
            incrementCounts[pos]--;
            pos++;
        }
        else
            DE_ASSERT(false);

        if (pos < 0 || pos >= (int)incrementCounts.size())
            break;
    }

    if (minValue + pos != lastValue)
        isOk = false;

    for (int valueNdx = 0; valueNdx < (int)incrementCounts.size(); valueNdx++)
    {
        if (incrementCounts[valueNdx] != 0)
            isOk = false;
    }

    for (int valueNdx = 0; valueNdx < (int)decrementCounts.size(); valueNdx++)
    {
        if (decrementCounts[valueNdx] != 0)
            isOk = false;
    }

    return isOk;
}

bool AtomicCounterTest::checkAndLogCallValues(TestLog &log, const vector<uint32_t> &increments,
                                              const vector<uint32_t> &decrements, const vector<uint32_t> &preGets,
                                              const vector<uint32_t> &postGets, const vector<uint32_t> &gets) const
{
    bool isOk = true;

    for (int counterNdx = 0; counterNdx < m_spec.atomicCounterCount; counterNdx++)
    {
        vector<uint32_t> counterIncrements;
        vector<uint32_t> counterDecrements;
        vector<uint32_t> counterPreGets;
        vector<uint32_t> counterPostGets;
        vector<uint32_t> counterGets;

        getCountersValues(counterIncrements, increments, counterNdx, m_spec.atomicCounterCount);
        getCountersValues(counterDecrements, decrements, counterNdx, m_spec.atomicCounterCount);
        getCountersValues(counterPreGets, preGets, counterNdx, m_spec.atomicCounterCount);
        getCountersValues(counterPostGets, postGets, counterNdx, m_spec.atomicCounterCount);
        getCountersValues(counterGets, gets, counterNdx, m_spec.atomicCounterCount);

        if (m_spec.operations == OPERATION_GET)
        {
            tcu::ScopedLogSection valueCheck(
                log, ("counter" + de::toString(counterNdx) + " value check").c_str(),
                ("Check that counter" + de::toString(counterNdx) + " values haven't changed.").c_str());
            int changedValues = 0;

            for (int valueNdx = 0; valueNdx < (int)gets.size(); valueNdx++)
            {
                if ((!m_spec.useBranches || gets[valueNdx] != (uint32_t)-1) && gets[valueNdx] != getInitialValue())
                {
                    if (changedValues < 20)
                        log << TestLog::Message << "atomicCounter(counter" << counterNdx << ") returned "
                            << gets[valueNdx] << " expected " << getInitialValue() << TestLog::EndMessage;
                    isOk = false;
                    changedValues++;
                }
            }

            if (changedValues == 0)
                log << TestLog::Message << "All values returned by atomicCounter(counter" << counterNdx
                    << ") match initial value " << getInitialValue() << "." << TestLog::EndMessage;
            else if (changedValues > 20)
                log << TestLog::Message << "Total number of invalid values returned by atomicCounter(counter"
                    << counterNdx << ") " << changedValues << " displaying first 20 values." << TestLog::EndMessage;
        }
        else if ((m_spec.operations & (OPERATION_INC | OPERATION_DEC)) == (OPERATION_INC | OPERATION_DEC))
        {
            tcu::ScopedLogSection valueCheck(log, ("counter" + de::toString(counterNdx) + " path check").c_str(),
                                             ("Check that there is order in which counter" + de::toString(counterNdx) +
                                              " increments and decrements could have happened.")
                                                 .c_str());
            if (!checkPath(counterIncrements, counterDecrements, getInitialValue(), m_spec))
            {
                isOk = false;
                log << TestLog::Message << "No possible order of calls to atomicCounterIncrement(counter" << counterNdx
                    << ") and atomicCounterDecrement(counter" << counterNdx << ") found." << TestLog::EndMessage;
            }
            else
                log << TestLog::Message << "Found possible order of calls to atomicCounterIncrement(counter"
                    << counterNdx << ") and atomicCounterDecrement(counter" << counterNdx << ")."
                    << TestLog::EndMessage;
        }
        else if ((m_spec.operations & OPERATION_INC) != 0)
        {
            {
                tcu::ScopedLogSection uniquenesCheck(
                    log, ("counter" + de::toString(counterNdx) + " check uniqueness and linearity").c_str(),
                    ("Check that counter" + de::toString(counterNdx) + " returned only unique and linear values.")
                        .c_str());

                if (!checkUniquenessAndLinearity(log, counterIncrements))
                {
                    isOk = false;
                    log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx
                        << ") returned non unique values." << TestLog::EndMessage;
                }
                else
                    log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx
                        << ") returned only unique values." << TestLog::EndMessage;
            }

            if (isOk && ((m_spec.operations & OPERATION_GET) != 0))
            {
                tcu::ScopedLogSection uniquenesCheck(
                    log, ("counter" + de::toString(counterNdx) + " check range").c_str(),
                    ("Check that counter" + de::toString(counterNdx) +
                     " returned only values values between previous and next atomicCounter(counter" +
                     de::toString(counterNdx) + ").")
                        .c_str());

                if (!checkRange(log, counterIncrements, counterPreGets, counterPostGets))
                {
                    isOk = false;
                    log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx
                        << ") returned value that is not between previous and next call to atomicCounter(counter"
                        << counterNdx << ")." << TestLog::EndMessage;
                }
                else
                    log << TestLog::Message << "atomicCounterIncrement(counter" << counterNdx
                        << ") returned only values between previous and next call to atomicCounter(counter"
                        << counterNdx << ")." << TestLog::EndMessage;
            }
        }
        else if ((m_spec.operations & OPERATION_DEC) != 0)
        {
            {
                tcu::ScopedLogSection uniquenesCheck(
                    log, ("counter" + de::toString(counterNdx) + " check uniqueness and linearity").c_str(),
                    ("Check that counter" + de::toString(counterNdx) + " returned only unique and linear values.")
                        .c_str());

                if (!checkUniquenessAndLinearity(log, counterDecrements))
                {
                    isOk = false;
                    log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx
                        << ") returned non unique values." << TestLog::EndMessage;
                }
                else
                    log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx
                        << ") returned only unique values." << TestLog::EndMessage;
            }

            if (isOk && ((m_spec.operations & OPERATION_GET) != 0))
            {
                tcu::ScopedLogSection uniquenesCheck(
                    log, ("counter" + de::toString(counterNdx) + " check range").c_str(),
                    ("Check that counter" + de::toString(counterNdx) +
                     " returned only values values between previous and next atomicCounter(counter" +
                     de::toString(counterNdx) + ".")
                        .c_str());

                if (!checkRange(log, counterDecrements, counterPostGets, counterPreGets))
                {
                    isOk = false;
                    log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx
                        << ") returned value that is not between previous and next call to atomicCounter(counter"
                        << counterNdx << ")." << TestLog::EndMessage;
                }
                else
                    log << TestLog::Message << "atomicCounterDecrement(counter" << counterNdx
                        << ") returned only values between previous and next call to atomicCounter(counter"
                        << counterNdx << ")." << TestLog::EndMessage;
            }
        }
    }

    return isOk;
}

TestCase::IterateResult AtomicCounterTest::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();
    const glu::Buffer counterBuffer(m_context.getRenderContext());
    const glu::Buffer outputBuffer(m_context.getRenderContext());
    const glu::ShaderProgram program(m_context.getRenderContext(),
                                     glu::ProgramSources()
                                         << glu::ShaderSource(glu::SHADERTYPE_COMPUTE, generateShaderSource(m_spec)));

    const int32_t counterBufferSize = m_spec.atomicCounterCount * 4;
    const int32_t ssoSize = m_spec.atomicCounterCount * m_spec.callCount * m_spec.threadCount * 4 * getOperationCount();

    log << program;

    if (m_spec.offsetType == OFFSETTYPE_INVALID || m_spec.offsetType == OFFSETTYPE_INVALID_DEFAULT ||
        m_spec.bindingType == BINDINGTYPE_INVALID || m_spec.bindingType == BINDINGTYPE_INVALID_DEFAULT ||
        m_spec.offsetType == OFFSETTYPE_INVALID_OVERLAPPING)
    {
        if (program.isOk())
        {
            log << TestLog::Message << "Expected program to fail, but compilation passed." << TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile succeeded");
            return STOP;
        }
        else
        {
            log << TestLog::Message << "Compilation failed as expected." << TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Compile failed");
            return STOP;
        }
    }
    else if (!program.isOk())
    {
        log << TestLog::Message << "Compile failed." << TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
        return STOP;
    }

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

    // Create output buffer
    gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
    gl.bufferData(GL_SHADER_STORAGE_BUFFER, ssoSize, NULL, GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create output buffer");

    // Create atomic counter buffer
    {
        vector<uint32_t> data(m_spec.atomicCounterCount, getInitialValue());
        gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *counterBuffer);
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, counterBufferSize, &(data[0]), GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create buffer for atomic counters");
    }

    // Bind output buffer
    gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *outputBuffer);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup output buffer");

    // Bind atomic counter buffer
    gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, *counterBuffer);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup atomic counter buffer");

    // Dispath compute
    gl.dispatchCompute(m_spec.threadCount, 1, 1);
    GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

    gl.finish();
    GLU_EXPECT_NO_ERROR(gl.getError(), "glFinish()");

    vector<uint32_t> output(ssoSize / 4, 0);
    vector<uint32_t> counters(m_spec.atomicCounterCount, 0);

    // Read back output buffer
    {
        gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");

        void *ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)(output.size() * sizeof(uint32_t)),
                                      GL_MAP_READ_BIT);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");

        deMemcpy(&(output[0]), ptr, (int)output.size() * sizeof(uint32_t));

        if (!gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER))
        {
            GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
            TCU_CHECK_MSG(false, "Mapped buffer corrupted");
        }

        gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
    }

    // Read back counter buffer
    {
        gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *counterBuffer);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");

        void *ptr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, (GLsizeiptr)(counters.size() * sizeof(uint32_t)),
                                      GL_MAP_READ_BIT);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");

        deMemcpy(&(counters[0]), ptr, (int)counters.size() * sizeof(uint32_t));

        if (!gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER))
        {
            GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
            TCU_CHECK_MSG(false, "Mapped buffer corrupted");
        }

        gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBuffer()");
    }

    bool isOk = true;

    if (!checkAndLogCounterValues(log, counters))
        isOk = false;

    {
        vector<uint32_t> increments;
        vector<uint32_t> decrements;
        vector<uint32_t> preGets;
        vector<uint32_t> postGets;
        vector<uint32_t> gets;

        splitBuffer(output, increments, decrements, preGets, postGets, gets);

        if (!checkAndLogCallValues(log, increments, decrements, preGets, postGets, gets))
            isOk = false;
    }

    if (isOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

    return STOP;
}

string specToTestName(const AtomicCounterTest::TestSpec &spec)
{
    std::ostringstream stream;

    stream << spec.atomicCounterCount << (spec.atomicCounterCount == 1 ? "_counter" : "_counters");
    stream << "_" << spec.callCount << (spec.callCount == 1 ? "_call" : "_calls");
    stream << "_" << spec.threadCount << (spec.threadCount == 1 ? "_thread" : "_threads");

    return stream.str();
}

string specToTestDescription(const AtomicCounterTest::TestSpec &spec)
{
    std::ostringstream stream;
    bool firstOperation = 0;

    stream << "Test ";

    if ((spec.operations & AtomicCounterTest::OPERATION_GET) != 0)
    {
        stream << "atomicCounter()";
        firstOperation = false;
    }

    if ((spec.operations & AtomicCounterTest::OPERATION_INC) != 0)
    {
        if (!firstOperation)
            stream << ", ";

        stream << " atomicCounterIncrement()";
        firstOperation = false;
    }

    if ((spec.operations & AtomicCounterTest::OPERATION_DEC) != 0)
    {
        if (!firstOperation)
            stream << ", ";

        stream << " atomicCounterDecrement()";
        firstOperation = false;
    }

    stream << " calls with ";

    if (spec.useBranches)
        stream << " branches, ";

    stream << spec.atomicCounterCount << " atomic counters, " << spec.callCount << " calls and " << spec.threadCount
           << " threads.";

    return stream.str();
}

string operationToName(const AtomicCounterTest::Operation &operations, bool useBranch)
{
    std::ostringstream stream;
    bool first = true;

    if ((operations & AtomicCounterTest::OPERATION_GET) != 0)
    {
        stream << "get";
        first = false;
    }

    if ((operations & AtomicCounterTest::OPERATION_INC) != 0)
    {
        if (!first)
            stream << "_";

        stream << "inc";
        first = false;
    }

    if ((operations & AtomicCounterTest::OPERATION_DEC) != 0)
    {
        if (!first)
            stream << "_";

        stream << "dec";
        first = false;
    }

    if (useBranch)
        stream << "_branch";

    return stream.str();
}

string operationToDescription(const AtomicCounterTest::Operation &operations, bool useBranch)
{
    std::ostringstream stream;
    bool firstOperation = 0;

    stream << "Test ";

    if ((operations & AtomicCounterTest::OPERATION_GET) != 0)
    {
        stream << "atomicCounter()";
        firstOperation = false;
    }

    if ((operations & AtomicCounterTest::OPERATION_INC) != 0)
    {
        if (!firstOperation)
            stream << ", ";

        stream << " atomicCounterIncrement()";
        firstOperation = false;
    }

    if ((operations & AtomicCounterTest::OPERATION_DEC) != 0)
    {
        if (!firstOperation)
            stream << ", ";

        stream << " atomicCounterDecrement()";
        firstOperation = false;
    }

    if (useBranch)
        stream << " calls with branches.";
    else
        stream << ".";

    return stream.str();
}

string layoutTypesToName(const AtomicCounterTest::BindingType &bindingType,
                         const AtomicCounterTest::OffsetType &offsetType)
{
    std::ostringstream stream;

    switch (bindingType)
    {
    case AtomicCounterTest::BINDINGTYPE_BASIC:
        // Nothing
        break;

    case AtomicCounterTest::BINDINGTYPE_INVALID:
        stream << "invalid_binding";
        break;

    default:
        DE_ASSERT(false);
    }

    if (bindingType != AtomicCounterTest::BINDINGTYPE_BASIC && offsetType != AtomicCounterTest::OFFSETTYPE_NONE)
        stream << "_";

    switch (offsetType)
    {
    case AtomicCounterTest::OFFSETTYPE_BASIC:
        stream << "basic_offset";
        break;

    case AtomicCounterTest::OFFSETTYPE_REVERSE:
        stream << "reverse_offset";
        break;

    case AtomicCounterTest::OFFSETTYPE_INVALID:
        stream << "invalid_offset";
        break;

    case AtomicCounterTest::OFFSETTYPE_FIRST_AUTO:
        stream << "first_offset_set";
        break;

    case AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO:
        stream << "default_offset_set";
        break;

    case AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT:
        stream << "reset_default_offset";
        break;

    case AtomicCounterTest::OFFSETTYPE_NONE:
        // Do nothing
        break;

    default:
        DE_ASSERT(false);
    }

    return stream.str();
}

string layoutTypesToDesc(const AtomicCounterTest::BindingType &bindingType,
                         const AtomicCounterTest::OffsetType &offsetType)
{
    std::ostringstream stream;

    switch (bindingType)
    {
    case AtomicCounterTest::BINDINGTYPE_BASIC:
        stream << "Test using atomic counters with explicit layout bindings and";
        break;

    case AtomicCounterTest::BINDINGTYPE_INVALID:
        stream << "Test using atomic counters with invalid explicit layout bindings and";
        break;

    case AtomicCounterTest::BINDINGTYPE_INVALID_DEFAULT:
        stream << "Test using atomic counters with invalid default layout binding and";
        break;

    default:
        DE_ASSERT(false);
    }

    switch (offsetType)
    {
    case AtomicCounterTest::OFFSETTYPE_NONE:
        stream << " no explicit offsets.";
        break;

    case AtomicCounterTest::OFFSETTYPE_BASIC:
        stream << "explicit continuos offsets.";
        break;

    case AtomicCounterTest::OFFSETTYPE_REVERSE:
        stream << "reversed explicit offsets.";
        break;

    case AtomicCounterTest::OFFSETTYPE_INVALID:
        stream << "invalid explicit offsets.";
        break;

    case AtomicCounterTest::OFFSETTYPE_FIRST_AUTO:
        stream << "only first counter with explicit offset.";
        break;

    case AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO:
        stream << "default offset.";
        break;

    case AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT:
        stream << "default offset specified twice.";
        break;

    default:
        DE_ASSERT(false);
    }

    return stream.str();
}

} // namespace

AtomicCounterTests::AtomicCounterTests(Context &context)
    : TestCaseGroup(context, "atomic_counter", "Atomic counter tests")
{
    // Runtime use tests
    {
        const int counterCounts[] = {1, 4, 8};

        const int callCounts[] = {1, 5, 100};

        const int threadCounts[] = {1, 10, 5000};

        const AtomicCounterTest::Operation operations[] = {
            AtomicCounterTest::OPERATION_GET,
            AtomicCounterTest::OPERATION_INC,
            AtomicCounterTest::OPERATION_DEC,

            (AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC | AtomicCounterTest::OPERATION_GET),
            (AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_DEC | AtomicCounterTest::OPERATION_GET),

            (AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC | AtomicCounterTest::OPERATION_DEC),
            (AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC | AtomicCounterTest::OPERATION_DEC |
                                           AtomicCounterTest::OPERATION_GET)};

        for (int operationNdx = 0; operationNdx < DE_LENGTH_OF_ARRAY(operations); operationNdx++)
        {
            const AtomicCounterTest::Operation operation = operations[operationNdx];

            for (int branch = 0; branch < 2; branch++)
            {
                const bool useBranch = (branch == 1);

                TestCaseGroup *operationGroup =
                    new TestCaseGroup(m_context, operationToName(operation, useBranch).c_str(),
                                      operationToDescription(operation, useBranch).c_str());

                for (int counterCountNdx = 0; counterCountNdx < DE_LENGTH_OF_ARRAY(counterCounts); counterCountNdx++)
                {
                    const int counterCount = counterCounts[counterCountNdx];

                    for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); callCountNdx++)
                    {
                        const int callCount = callCounts[callCountNdx];

                        for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts);
                             threadCountNdx++)
                        {
                            const int threadCount = threadCounts[threadCountNdx];

                            if (threadCount * callCount * counterCount > 10000)
                                continue;

                            if (useBranch && threadCount * callCount == 1)
                                continue;

                            AtomicCounterTest::TestSpec spec;

                            spec.atomicCounterCount = counterCount;
                            spec.operations         = operation;
                            spec.callCount          = callCount;
                            spec.useBranches        = useBranch;
                            spec.threadCount        = threadCount;
                            spec.bindingType        = AtomicCounterTest::BINDINGTYPE_BASIC;
                            spec.offsetType         = AtomicCounterTest::OFFSETTYPE_NONE;

                            operationGroup->addChild(new AtomicCounterTest(m_context, specToTestName(spec).c_str(),
                                                                           specToTestDescription(spec).c_str(), spec));
                        }
                    }
                }

                addChild(operationGroup);
            }
        }
    }

    {
        TestCaseGroup *layoutGroup = new TestCaseGroup(m_context, "layout", "Layout qualifier tests.");

        const int counterCounts[] = {1, 8};
        const int callCounts[]    = {1, 5};
        const int threadCounts[]  = {1, 1000};

        const AtomicCounterTest::Operation operations[] = {
            (AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC | AtomicCounterTest::OPERATION_GET),
            (AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_DEC | AtomicCounterTest::OPERATION_GET),
            (AtomicCounterTest::Operation)(AtomicCounterTest::OPERATION_INC | AtomicCounterTest::OPERATION_DEC)};

        const AtomicCounterTest::OffsetType offsetTypes[] = {
            AtomicCounterTest::OFFSETTYPE_REVERSE, AtomicCounterTest::OFFSETTYPE_FIRST_AUTO,
            AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO, AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT};

        for (int offsetTypeNdx = 0; offsetTypeNdx < DE_LENGTH_OF_ARRAY(offsetTypes); offsetTypeNdx++)
        {
            const AtomicCounterTest::OffsetType offsetType = offsetTypes[offsetTypeNdx];

            TestCaseGroup *layoutQualifierGroup = new TestCaseGroup(
                m_context, layoutTypesToName(AtomicCounterTest::BINDINGTYPE_BASIC, offsetType).c_str(),
                layoutTypesToDesc(AtomicCounterTest::BINDINGTYPE_BASIC, offsetType).c_str());

            for (int operationNdx = 0; operationNdx < DE_LENGTH_OF_ARRAY(operations); operationNdx++)
            {
                const AtomicCounterTest::Operation operation = operations[operationNdx];

                TestCaseGroup *operationGroup = new TestCaseGroup(m_context, operationToName(operation, false).c_str(),
                                                                  operationToDescription(operation, false).c_str());

                for (int counterCountNdx = 0; counterCountNdx < DE_LENGTH_OF_ARRAY(counterCounts); counterCountNdx++)
                {
                    const int counterCount = counterCounts[counterCountNdx];

                    if (offsetType == AtomicCounterTest::OFFSETTYPE_FIRST_AUTO && counterCount < 3)
                        continue;

                    if (offsetType == AtomicCounterTest::OFFSETTYPE_DEFAULT_AUTO && counterCount < 2)
                        continue;

                    if (offsetType == AtomicCounterTest::OFFSETTYPE_RESET_DEFAULT && counterCount < 2)
                        continue;

                    if (offsetType == AtomicCounterTest::OFFSETTYPE_REVERSE && counterCount < 2)
                        continue;

                    for (int callCountNdx = 0; callCountNdx < DE_LENGTH_OF_ARRAY(callCounts); callCountNdx++)
                    {
                        const int callCount = callCounts[callCountNdx];

                        for (int threadCountNdx = 0; threadCountNdx < DE_LENGTH_OF_ARRAY(threadCounts);
                             threadCountNdx++)
                        {
                            const int threadCount = threadCounts[threadCountNdx];

                            AtomicCounterTest::TestSpec spec;

                            spec.atomicCounterCount = counterCount;
                            spec.operations         = operation;
                            spec.callCount          = callCount;
                            spec.useBranches        = false;
                            spec.threadCount        = threadCount;
                            spec.bindingType        = AtomicCounterTest::BINDINGTYPE_BASIC;
                            spec.offsetType         = offsetType;

                            operationGroup->addChild(new AtomicCounterTest(m_context, specToTestName(spec).c_str(),
                                                                           specToTestDescription(spec).c_str(), spec));
                        }
                    }
                }
                layoutQualifierGroup->addChild(operationGroup);
            }
            layoutGroup->addChild(layoutQualifierGroup);
        }

        {
            TestCaseGroup *invalidGroup = new TestCaseGroup(m_context, "invalid", "Test invalid layouts");

            {
                AtomicCounterTest::TestSpec spec;

                spec.atomicCounterCount = 1;
                spec.operations         = AtomicCounterTest::OPERATION_INC;
                spec.callCount          = 1;
                spec.useBranches        = false;
                spec.threadCount        = 1;
                spec.bindingType        = AtomicCounterTest::BINDINGTYPE_INVALID;
                spec.offsetType         = AtomicCounterTest::OFFSETTYPE_NONE;

                invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_binding",
                                                             "Test layout qualifiers with invalid binding.", spec));
            }

            {
                AtomicCounterTest::TestSpec spec;

                spec.atomicCounterCount = 1;
                spec.operations         = AtomicCounterTest::OPERATION_INC;
                spec.callCount          = 1;
                spec.useBranches        = false;
                spec.threadCount        = 1;
                spec.bindingType        = AtomicCounterTest::BINDINGTYPE_INVALID_DEFAULT;
                spec.offsetType         = AtomicCounterTest::OFFSETTYPE_NONE;

                invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_default_binding",
                                                             "Test layout qualifiers with invalid default binding.",
                                                             spec));
            }

            {
                AtomicCounterTest::TestSpec spec;

                spec.atomicCounterCount = 1;
                spec.operations         = AtomicCounterTest::OPERATION_INC;
                spec.callCount          = 1;
                spec.useBranches        = false;
                spec.threadCount        = 1;
                spec.bindingType        = AtomicCounterTest::BINDINGTYPE_BASIC;
                spec.offsetType         = AtomicCounterTest::OFFSETTYPE_INVALID;

                invalidGroup->addChild(new AtomicCounterTest(
                    m_context, "invalid_offset_align", "Test layout qualifiers with invalid alignment offset.", spec));
            }

            {
                AtomicCounterTest::TestSpec spec;

                spec.atomicCounterCount = 2;
                spec.operations         = AtomicCounterTest::OPERATION_INC;
                spec.callCount          = 1;
                spec.useBranches        = false;
                spec.threadCount        = 1;
                spec.bindingType        = AtomicCounterTest::BINDINGTYPE_BASIC;
                spec.offsetType         = AtomicCounterTest::OFFSETTYPE_INVALID_OVERLAPPING;

                invalidGroup->addChild(new AtomicCounterTest(m_context, "invalid_offset_overlap",
                                                             "Test layout qualifiers with invalid overlapping offset.",
                                                             spec));
            }

            {
                AtomicCounterTest::TestSpec spec;

                spec.atomicCounterCount = 1;
                spec.operations         = AtomicCounterTest::OPERATION_INC;
                spec.callCount          = 1;
                spec.useBranches        = false;
                spec.threadCount        = 1;
                spec.bindingType        = AtomicCounterTest::BINDINGTYPE_BASIC;
                spec.offsetType         = AtomicCounterTest::OFFSETTYPE_INVALID_DEFAULT;

                invalidGroup->addChild(new AtomicCounterTest(
                    m_context, "invalid_default_offset", "Test layout qualifiers with invalid default offset.", spec));
            }

            layoutGroup->addChild(invalidGroup);
        }

        addChild(layoutGroup);
    }
}

AtomicCounterTests::~AtomicCounterTests(void)
{
}

void AtomicCounterTests::init(void)
{
}

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