/*-------------------------------------------------------------------------
 * 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 Opaque type (sampler, buffer, atomic counter, ...) indexing tests.
 *
 * \todo [2014-03-05 pyry] Extend with following:
 *  + sampler: different filtering modes, multiple sizes, incomplete textures
 *  + SSBO: write, atomic op, unsized array .length()
 *//*--------------------------------------------------------------------*/

#include "es31fOpaqueTypeIndexingTests.hpp"
#include "tcuTexture.hpp"
#include "tcuTestLog.hpp"
#include "tcuFormatUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "gluShaderUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluTextureUtil.hpp"
#include "gluRenderContext.hpp"
#include "gluProgramInterfaceQuery.hpp"
#include "gluContextInfo.hpp"
#include "glsShaderExecUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"

#include <sstream>

namespace deqp
{
namespace gles31
{
namespace Functional
{

namespace
{

using namespace gls::ShaderExecUtil;
using namespace glu;
using std::string;
using std::vector;
using tcu::TestLog;
using tcu::TextureFormat;

typedef de::UniquePtr<ShaderExecutor> ShaderExecutorPtr;

enum IndexExprType
{
    INDEX_EXPR_TYPE_CONST_LITERAL = 0,
    INDEX_EXPR_TYPE_CONST_EXPRESSION,
    INDEX_EXPR_TYPE_UNIFORM,
    INDEX_EXPR_TYPE_DYNAMIC_UNIFORM,

    INDEX_EXPR_TYPE_LAST
};

enum TextureType
{
    TEXTURE_TYPE_1D = 0,
    TEXTURE_TYPE_2D,
    TEXTURE_TYPE_CUBE,
    TEXTURE_TYPE_2D_ARRAY,
    TEXTURE_TYPE_3D,
    TEXTURE_TYPE_CUBE_ARRAY,

    TEXTURE_TYPE_LAST
};

static void declareUniformIndexVars(std::ostream &str, const char *varPrefix, int numVars)
{
    for (int varNdx = 0; varNdx < numVars; varNdx++)
        str << "uniform highp int " << varPrefix << varNdx << ";\n";
}

static void uploadUniformIndices(const glw::Functions &gl, uint32_t program, const char *varPrefix, int numIndices,
                                 const int *indices)
{
    for (int varNdx = 0; varNdx < numIndices; varNdx++)
    {
        const string varName = varPrefix + de::toString(varNdx);
        const int loc        = gl.getUniformLocation(program, varName.c_str());
        TCU_CHECK_MSG(loc >= 0, ("No location assigned for uniform '" + varName + "'").c_str());

        gl.uniform1i(loc, indices[varNdx]);
    }
}

template <typename T>
static T maxElement(const std::vector<T> &elements)
{
    T maxElem = elements[0];

    for (size_t ndx = 1; ndx < elements.size(); ndx++)
        maxElem = de::max(maxElem, elements[ndx]);

    return maxElem;
}

static TextureType getTextureType(glu::DataType samplerType)
{
    switch (samplerType)
    {
    case glu::TYPE_SAMPLER_1D:
    case glu::TYPE_INT_SAMPLER_1D:
    case glu::TYPE_UINT_SAMPLER_1D:
    case glu::TYPE_SAMPLER_1D_SHADOW:
        return TEXTURE_TYPE_1D;

    case glu::TYPE_SAMPLER_2D:
    case glu::TYPE_INT_SAMPLER_2D:
    case glu::TYPE_UINT_SAMPLER_2D:
    case glu::TYPE_SAMPLER_2D_SHADOW:
        return TEXTURE_TYPE_2D;

    case glu::TYPE_SAMPLER_CUBE:
    case glu::TYPE_INT_SAMPLER_CUBE:
    case glu::TYPE_UINT_SAMPLER_CUBE:
    case glu::TYPE_SAMPLER_CUBE_SHADOW:
        return TEXTURE_TYPE_CUBE;

    case glu::TYPE_SAMPLER_2D_ARRAY:
    case glu::TYPE_INT_SAMPLER_2D_ARRAY:
    case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
    case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
        return TEXTURE_TYPE_2D_ARRAY;

    case glu::TYPE_SAMPLER_3D:
    case glu::TYPE_INT_SAMPLER_3D:
    case glu::TYPE_UINT_SAMPLER_3D:
        return TEXTURE_TYPE_3D;

    case glu::TYPE_SAMPLER_CUBE_ARRAY:
    case glu::TYPE_SAMPLER_CUBE_ARRAY_SHADOW:
    case glu::TYPE_INT_SAMPLER_CUBE_ARRAY:
    case glu::TYPE_UINT_SAMPLER_CUBE_ARRAY:
        return TEXTURE_TYPE_CUBE_ARRAY;

    default:
        TCU_THROW(InternalError, "Invalid sampler type");
    }
}

static bool isShadowSampler(glu::DataType samplerType)
{
    return samplerType == glu::TYPE_SAMPLER_1D_SHADOW || samplerType == glu::TYPE_SAMPLER_2D_SHADOW ||
           samplerType == glu::TYPE_SAMPLER_2D_ARRAY_SHADOW || samplerType == glu::TYPE_SAMPLER_CUBE_SHADOW ||
           samplerType == glu::TYPE_SAMPLER_CUBE_ARRAY_SHADOW;
}

static glu::DataType getSamplerOutputType(glu::DataType samplerType)
{
    switch (samplerType)
    {
    case glu::TYPE_SAMPLER_1D:
    case glu::TYPE_SAMPLER_2D:
    case glu::TYPE_SAMPLER_CUBE:
    case glu::TYPE_SAMPLER_2D_ARRAY:
    case glu::TYPE_SAMPLER_3D:
    case glu::TYPE_SAMPLER_CUBE_ARRAY:
        return glu::TYPE_FLOAT_VEC4;

    case glu::TYPE_SAMPLER_1D_SHADOW:
    case glu::TYPE_SAMPLER_2D_SHADOW:
    case glu::TYPE_SAMPLER_CUBE_SHADOW:
    case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
    case glu::TYPE_SAMPLER_CUBE_ARRAY_SHADOW:
        return glu::TYPE_FLOAT;

    case glu::TYPE_INT_SAMPLER_1D:
    case glu::TYPE_INT_SAMPLER_2D:
    case glu::TYPE_INT_SAMPLER_CUBE:
    case glu::TYPE_INT_SAMPLER_2D_ARRAY:
    case glu::TYPE_INT_SAMPLER_3D:
    case glu::TYPE_INT_SAMPLER_CUBE_ARRAY:
        return glu::TYPE_INT_VEC4;

    case glu::TYPE_UINT_SAMPLER_1D:
    case glu::TYPE_UINT_SAMPLER_2D:
    case glu::TYPE_UINT_SAMPLER_CUBE:
    case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
    case glu::TYPE_UINT_SAMPLER_3D:
    case glu::TYPE_UINT_SAMPLER_CUBE_ARRAY:
        return glu::TYPE_UINT_VEC4;

    default:
        TCU_THROW(InternalError, "Invalid sampler type");
    }
}

static tcu::TextureFormat getSamplerTextureFormat(glu::DataType samplerType)
{
    const glu::DataType outType       = getSamplerOutputType(samplerType);
    const glu::DataType outScalarType = glu::getDataTypeScalarType(outType);

    switch (outScalarType)
    {
    case glu::TYPE_FLOAT:
        if (isShadowSampler(samplerType))
            return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
        else
            return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);

    case glu::TYPE_INT:
        return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT8);
    case glu::TYPE_UINT:
        return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT8);

    default:
        TCU_THROW(InternalError, "Invalid sampler type");
    }
}

static glu::DataType getSamplerCoordType(glu::DataType samplerType)
{
    const TextureType texType = getTextureType(samplerType);
    int numCoords             = 0;

    switch (texType)
    {
    case TEXTURE_TYPE_1D:
        numCoords = 1;
        break;
    case TEXTURE_TYPE_2D:
        numCoords = 2;
        break;
    case TEXTURE_TYPE_2D_ARRAY:
        numCoords = 3;
        break;
    case TEXTURE_TYPE_CUBE:
        numCoords = 3;
        break;
    case TEXTURE_TYPE_3D:
        numCoords = 3;
        break;
    case TEXTURE_TYPE_CUBE_ARRAY:
        numCoords = 4;
        break;
    default:
        TCU_THROW(InternalError, "Invalid texture type");
    }

    if (isShadowSampler(samplerType) && samplerType != TYPE_SAMPLER_CUBE_ARRAY_SHADOW)
        numCoords += 1;

    DE_ASSERT(de::inRange(numCoords, 1, 4));

    return numCoords == 1 ? glu::TYPE_FLOAT : glu::getDataTypeFloatVec(numCoords);
}

static uint32_t getGLTextureTarget(TextureType texType)
{
    switch (texType)
    {
    case TEXTURE_TYPE_1D:
        return GL_TEXTURE_1D;
    case TEXTURE_TYPE_2D:
        return GL_TEXTURE_2D;
    case TEXTURE_TYPE_2D_ARRAY:
        return GL_TEXTURE_2D_ARRAY;
    case TEXTURE_TYPE_CUBE:
        return GL_TEXTURE_CUBE_MAP;
    case TEXTURE_TYPE_3D:
        return GL_TEXTURE_3D;
    case TEXTURE_TYPE_CUBE_ARRAY:
        return GL_TEXTURE_CUBE_MAP_ARRAY;
    default:
        TCU_THROW(InternalError, "Invalid texture type");
    }
}

static void setupTexture(const glw::Functions &gl, uint32_t texture, glu::DataType samplerType,
                         tcu::TextureFormat texFormat, const void *color)
{
    const TextureType texType             = getTextureType(samplerType);
    const uint32_t texTarget              = getGLTextureTarget(texType);
    const uint32_t intFormat              = glu::getInternalFormat(texFormat);
    const glu::TransferFormat transferFmt = glu::getTransferFormat(texFormat);

    // \todo [2014-03-04 pyry] Use larger than 1x1 textures?

    gl.bindTexture(texTarget, texture);

    switch (texType)
    {
    case TEXTURE_TYPE_1D:
        gl.texStorage1D(texTarget, 1, intFormat, 1);
        gl.texSubImage1D(texTarget, 0, 0, 1, transferFmt.format, transferFmt.dataType, color);
        break;

    case TEXTURE_TYPE_2D:
        gl.texStorage2D(texTarget, 1, intFormat, 1, 1);
        gl.texSubImage2D(texTarget, 0, 0, 0, 1, 1, transferFmt.format, transferFmt.dataType, color);
        break;

    case TEXTURE_TYPE_2D_ARRAY:
    case TEXTURE_TYPE_3D:
        gl.texStorage3D(texTarget, 1, intFormat, 1, 1, 1);
        gl.texSubImage3D(texTarget, 0, 0, 0, 0, 1, 1, 1, transferFmt.format, transferFmt.dataType, color);
        break;

    case TEXTURE_TYPE_CUBE_ARRAY:
        gl.texStorage3D(texTarget, 1, intFormat, 1, 1, 6);
        for (int zoffset = 0; zoffset < 6; ++zoffset)
            for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
                gl.texSubImage3D(texTarget, 0, 0, 0, zoffset, 1, 1, 1, transferFmt.format, transferFmt.dataType, color);
        break;

    case TEXTURE_TYPE_CUBE:
        gl.texStorage2D(texTarget, 1, intFormat, 1, 1);
        for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
            gl.texSubImage2D(glu::getGLCubeFace((tcu::CubeFace)face), 0, 0, 0, 1, 1, transferFmt.format,
                             transferFmt.dataType, color);
        break;

    default:
        TCU_THROW(InternalError, "Invalid texture type");
    }

    gl.texParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.texParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    if (isShadowSampler(samplerType))
        gl.texParameteri(texTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);

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

class SamplerIndexingCase : public TestCase
{
public:
    SamplerIndexingCase(Context &context, const char *name, const char *description, glu::ShaderType shaderType,
                        glu::DataType samplerType, IndexExprType indexExprType);
    ~SamplerIndexingCase(void);

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

private:
    SamplerIndexingCase(const SamplerIndexingCase &);
    SamplerIndexingCase &operator=(const SamplerIndexingCase &);

    void getShaderSpec(ShaderSpec *spec, int numSamplers, int numLookups, const int *lookupIndices,
                       const RenderContext &renderContext) const;

    const glu::ShaderType m_shaderType;
    const glu::DataType m_samplerType;
    const IndexExprType m_indexExprType;
};

SamplerIndexingCase::SamplerIndexingCase(Context &context, const char *name, const char *description,
                                         glu::ShaderType shaderType, glu::DataType samplerType,
                                         IndexExprType indexExprType)
    : TestCase(context, name, description)
    , m_shaderType(shaderType)
    , m_samplerType(samplerType)
    , m_indexExprType(indexExprType)
{
}

SamplerIndexingCase::~SamplerIndexingCase(void)
{
}

void SamplerIndexingCase::init(void)
{
    const bool supportsES32 = contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
                              hasExtension(m_context.getRenderContext().getFunctions(), glu::ApiType::core(4, 5),
                                           "GL_ARB_ES3_2_compatibility");

    if (!supportsES32)
    {
        if (m_shaderType == SHADERTYPE_GEOMETRY)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"),
                                "GL_EXT_geometry_shader extension is required to run geometry shader tests.");

        if (m_shaderType == SHADERTYPE_TESSELLATION_CONTROL || m_shaderType == SHADERTYPE_TESSELLATION_EVALUATION)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"),
                                "GL_EXT_tessellation_shader extension is required to run tessellation shader tests.");

        if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"),
                                "GL_EXT_gpu_shader5 extension is required for dynamic indexing of sampler arrays.");

        if (m_samplerType == TYPE_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_SAMPLER_CUBE_ARRAY_SHADOW ||
            m_samplerType == TYPE_INT_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_UINT_SAMPLER_CUBE_ARRAY)
        {
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_cube_map_array"),
                                "GL_EXT_texture_cube_map_array extension is required for cube map arrays.");
        }
    }
}

void SamplerIndexingCase::getShaderSpec(ShaderSpec *spec, int numSamplers, int numLookups, const int *lookupIndices,
                                        const RenderContext &renderContext) const
{
    const char *samplersName  = "sampler";
    const char *coordsName    = "coords";
    const char *indicesPrefix = "index";
    const char *resultPrefix  = "result";
    const DataType coordType  = getSamplerCoordType(m_samplerType);
    const DataType outType    = getSamplerOutputType(m_samplerType);
    const bool supportsES32 =
        contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) ||
        hasExtension(renderContext.getFunctions(), glu::ApiType::core(4, 5), "GL_ARB_ES3_2_compatibility");
    std::ostringstream global;
    std::ostringstream code;

    spec->inputs.push_back(Symbol(coordsName, VarType(coordType, PRECISION_HIGHP)));

    if (!supportsES32 && m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL &&
        m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION)
        global << "#extension GL_EXT_gpu_shader5 : require\n";

    if (!supportsES32 &&
        (m_samplerType == TYPE_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_SAMPLER_CUBE_ARRAY_SHADOW ||
         m_samplerType == TYPE_INT_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_UINT_SAMPLER_CUBE_ARRAY))
    {
        global << "#extension GL_EXT_texture_cube_map_array: require\n";
    }

    if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION)
        global << "const highp int indexBase = 1;\n";

    global << "uniform highp " << getDataTypeName(m_samplerType) << " " << samplersName << "[" << numSamplers << "];\n";

    if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
    {
        for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
        {
            const string varName = indicesPrefix + de::toString(lookupNdx);
            spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP)));
        }
    }
    else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
        declareUniformIndexVars(global, indicesPrefix, numLookups);

    for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
    {
        const string varName = resultPrefix + de::toString(lookupNdx);
        spec->outputs.push_back(Symbol(varName, VarType(outType, PRECISION_HIGHP)));
    }

    for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
    {
        code << resultPrefix << "" << lookupNdx << " = texture(" << samplersName << "[";

        if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL)
            code << lookupIndices[lookupNdx];
        else if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION)
            code << "indexBase + " << (lookupIndices[lookupNdx] - 1);
        else
            code << indicesPrefix << lookupNdx;

        code << "], " << coordsName << (m_samplerType == TYPE_SAMPLER_CUBE_ARRAY_SHADOW ? ", 0.0" : "") << ");\n";
    }

    spec->version            = supportsES32 ? GLSL_VERSION_320_ES : GLSL_VERSION_310_ES;
    spec->globalDeclarations = global.str();
    spec->source             = code.str();
}

static void fillTextureData(const tcu::PixelBufferAccess &access, de::Random &rnd)
{
    DE_ASSERT(access.getHeight() == 1 && access.getDepth() == 1);

    if (access.getFormat().order == TextureFormat::D)
    {
        // \note Texture uses odd values, lookup even values to avoid precision issues.
        const float values[] = {0.1f, 0.3f, 0.5f, 0.7f, 0.9f};

        for (int ndx = 0; ndx < access.getWidth(); ndx++)
            access.setPixDepth(rnd.choose<float>(DE_ARRAY_BEGIN(values), DE_ARRAY_END(values)), ndx, 0);
    }
    else
    {
        TCU_CHECK_INTERNAL(access.getFormat().order == TextureFormat::RGBA && access.getFormat().getPixelSize() == 4);

        for (int ndx = 0; ndx < access.getWidth(); ndx++)
            *((uint32_t *)access.getDataPtr() + ndx) = rnd.getUint32();
    }
}

SamplerIndexingCase::IterateResult SamplerIndexingCase::iterate(void)
{
    const int numInvocations      = 64;
    const int numSamplers         = 8;
    const int numLookups          = 4;
    const DataType coordType      = getSamplerCoordType(m_samplerType);
    const DataType outputType     = getSamplerOutputType(m_samplerType);
    const TextureFormat texFormat = getSamplerTextureFormat(m_samplerType);
    const int outLookupStride     = numInvocations * getDataTypeScalarSize(outputType);
    vector<int> lookupIndices(numLookups);
    vector<float> coords;
    vector<uint32_t> outData;
    vector<uint8_t> texData(numSamplers * texFormat.getPixelSize());
    const tcu::PixelBufferAccess refTexAccess(texFormat, numSamplers, 1, 1, &texData[0]);

    ShaderSpec shaderSpec;
    de::Random rnd(deInt32Hash(m_samplerType) ^ deInt32Hash(m_shaderType) ^ deInt32Hash(m_indexExprType));

    for (int ndx = 0; ndx < numLookups; ndx++)
        lookupIndices[ndx] = rnd.getInt(0, numSamplers - 1);

    getShaderSpec(&shaderSpec, numSamplers, numLookups, &lookupIndices[0], m_context.getRenderContext());

    coords.resize(numInvocations * getDataTypeScalarSize(coordType));

    if (m_samplerType != TYPE_SAMPLER_CUBE_ARRAY_SHADOW && isShadowSampler(m_samplerType))
    {
        // Use different comparison value per invocation.
        // \note Texture uses odd values, comparison even values.
        const int numCoordComps = getDataTypeScalarSize(coordType);
        const float cmpValues[] = {0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f};

        for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
            coords[invocationNdx * numCoordComps + (numCoordComps - 1)] =
                rnd.choose<float>(DE_ARRAY_BEGIN(cmpValues), DE_ARRAY_END(cmpValues));
    }

    fillTextureData(refTexAccess, rnd);

    outData.resize(numLookups * outLookupStride);

    {
        const RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl       = renderCtx.getFunctions();
        ShaderExecutorPtr executor(createExecutor(m_context.getRenderContext(), m_shaderType, shaderSpec));
        TextureVector textures(renderCtx, numSamplers);
        vector<void *> inputs;
        vector<void *> outputs;
        vector<int> expandedIndices;
        const int maxIndex = maxElement(lookupIndices);

        m_testCtx.getLog() << *executor;

        if (!executor->isOk())
            TCU_FAIL("Compile failed");

        executor->useProgram();

        // \todo [2014-03-05 pyry] Do we want to randomize tex unit assignments?
        for (int samplerNdx = 0; samplerNdx < numSamplers; samplerNdx++)
        {
            const string samplerName = string("sampler[") + de::toString(samplerNdx) + "]";
            const int samplerLoc     = gl.getUniformLocation(executor->getProgram(), samplerName.c_str());

            if (samplerNdx > maxIndex && samplerLoc < 0)
                continue; // Unused uniform eliminated by compiler

            TCU_CHECK_MSG(samplerLoc >= 0, (string("No location for uniform '") + samplerName + "' found").c_str());

            gl.activeTexture(GL_TEXTURE0 + samplerNdx);
            setupTexture(gl, textures[samplerNdx], m_samplerType, texFormat,
                         &texData[samplerNdx * texFormat.getPixelSize()]);

            gl.uniform1i(samplerLoc, samplerNdx);
        }

        inputs.push_back(&coords[0]);

        if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
        {
            expandedIndices.resize(numInvocations * lookupIndices.size());
            for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
            {
                for (int invNdx = 0; invNdx < numInvocations; invNdx++)
                    expandedIndices[lookupNdx * numInvocations + invNdx] = lookupIndices[lookupNdx];
            }

            for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
                inputs.push_back(&expandedIndices[lookupNdx * numInvocations]);
        }
        else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
            uploadUniformIndices(gl, executor->getProgram(), "index", numLookups, &lookupIndices[0]);

        for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
            outputs.push_back(&outData[outLookupStride * lookupNdx]);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

        executor->execute(numInvocations, &inputs[0], &outputs[0]);
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    if (isShadowSampler(m_samplerType))
    {
        const tcu::Sampler refSampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
                                      tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0.0f,
                                      false /* non-normalized */, tcu::Sampler::COMPAREMODE_LESS);
        const int numCoordComps = getDataTypeScalarSize(coordType);

        TCU_CHECK_INTERNAL(getDataTypeScalarSize(outputType) == 1);

        // Each invocation may have different results.
        for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
        {
            const float coord = coords[invocationNdx * numCoordComps + (numCoordComps - 1)];

            for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
            {
                const int texNdx = lookupIndices[lookupNdx];
                const float result =
                    *((const float *)(const uint8_t *)&outData[lookupNdx * outLookupStride + invocationNdx]);
                const float reference = refTexAccess.sample2DCompare(refSampler, tcu::Sampler::NEAREST, coord,
                                                                     (float)texNdx, 0.0f, tcu::IVec3(0));

                if (de::abs(result - reference) > 0.005f)
                {
                    m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx << ", lookup "
                                       << lookupNdx << ": expected " << reference << ", got " << result
                                       << TestLog::EndMessage;

                    if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid lookup result");
                }
            }
        }
    }
    else
    {
        TCU_CHECK_INTERNAL(getDataTypeScalarSize(outputType) == 4);

        // Validate results from first invocation
        for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
        {
            const int texNdx      = lookupIndices[lookupNdx];
            const uint8_t *resPtr = (const uint8_t *)&outData[lookupNdx * outLookupStride];
            bool isOk;

            if (outputType == TYPE_FLOAT_VEC4)
            {
                const float threshold     = 1.0f / 256.0f;
                const tcu::Vec4 reference = refTexAccess.getPixel(texNdx, 0);
                const float *floatPtr     = (const float *)resPtr;
                const tcu::Vec4 result(floatPtr[0], floatPtr[1], floatPtr[2], floatPtr[3]);

                isOk = boolAll(lessThanEqual(abs(reference - result), tcu::Vec4(threshold)));

                if (!isOk)
                {
                    m_testCtx.getLog() << TestLog::Message << "ERROR: at lookup " << lookupNdx << ": expected "
                                       << reference << ", got " << result << TestLog::EndMessage;
                }
            }
            else
            {
                const tcu::UVec4 reference = refTexAccess.getPixelUint(texNdx, 0);
                const uint32_t *uintPtr    = (const uint32_t *)resPtr;
                const tcu::UVec4 result(uintPtr[0], uintPtr[1], uintPtr[2], uintPtr[3]);

                isOk = boolAll(equal(reference, result));

                if (!isOk)
                {
                    m_testCtx.getLog() << TestLog::Message << "ERROR: at lookup " << lookupNdx << ": expected "
                                       << reference << ", got " << result << TestLog::EndMessage;
                }
            }

            if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid lookup result");
        }

        // Check results of other invocations against first one
        for (int invocationNdx = 1; invocationNdx < numInvocations; invocationNdx++)
        {
            for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++)
            {
                const uint32_t *refPtr = &outData[lookupNdx * outLookupStride];
                const uint32_t *resPtr = refPtr + invocationNdx * 4;
                bool isOk              = true;

                for (int ndx = 0; ndx < 4; ndx++)
                    isOk = isOk && (refPtr[ndx] == resPtr[ndx]);

                if (!isOk)
                {
                    m_testCtx.getLog() << TestLog::Message << "ERROR: invocation " << invocationNdx << " result "
                                       << tcu::formatArray(tcu::Format::HexIterator<uint32_t>(resPtr),
                                                           tcu::Format::HexIterator<uint32_t>(resPtr + 4))
                                       << " for lookup " << lookupNdx << " doesn't match result from first invocation "
                                       << tcu::formatArray(tcu::Format::HexIterator<uint32_t>(refPtr),
                                                           tcu::Format::HexIterator<uint32_t>(refPtr + 4))
                                       << TestLog::EndMessage;

                    if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsistent lookup results");
                }
            }
        }
    }

    return STOP;
}

class BlockArrayIndexingCase : public TestCase
{
public:
    enum BlockType
    {
        BLOCKTYPE_UNIFORM = 0,
        BLOCKTYPE_BUFFER,

        BLOCKTYPE_LAST
    };
    BlockArrayIndexingCase(Context &context, const char *name, const char *description, BlockType blockType,
                           IndexExprType indexExprType, ShaderType shaderType);
    ~BlockArrayIndexingCase(void);

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

private:
    BlockArrayIndexingCase(const BlockArrayIndexingCase &);
    BlockArrayIndexingCase &operator=(const BlockArrayIndexingCase &);

    void getShaderSpec(ShaderSpec *spec, int numInstances, int numReads, const int *readIndices,
                       const RenderContext &renderContext) const;

    const BlockType m_blockType;
    const IndexExprType m_indexExprType;
    const ShaderType m_shaderType;

    const int m_numInstances;
};

BlockArrayIndexingCase::BlockArrayIndexingCase(Context &context, const char *name, const char *description,
                                               BlockType blockType, IndexExprType indexExprType, ShaderType shaderType)
    : TestCase(context, name, description)
    , m_blockType(blockType)
    , m_indexExprType(indexExprType)
    , m_shaderType(shaderType)
    , m_numInstances(4)
{
}

BlockArrayIndexingCase::~BlockArrayIndexingCase(void)
{
}

void BlockArrayIndexingCase::init(void)
{
    const bool supportsES32 = contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
                              hasExtension(m_context.getRenderContext().getFunctions(), glu::ApiType::core(4, 5),
                                           "GL_ARB_ES3_2_compatibility");

    if (!supportsES32)
    {
        if (m_shaderType == SHADERTYPE_GEOMETRY)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"),
                                "GL_EXT_geometry_shader extension is required to run geometry shader tests.");

        if (m_shaderType == SHADERTYPE_TESSELLATION_CONTROL || m_shaderType == SHADERTYPE_TESSELLATION_EVALUATION)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"),
                                "GL_EXT_tessellation_shader extension is required to run tessellation shader tests.");

        if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"),
                                "GL_EXT_gpu_shader5 extension is required for dynamic indexing of interface blocks.");
    }

    if (m_blockType == BLOCKTYPE_BUFFER)
    {
        const uint32_t limitPnames[] = {
            GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS,          GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS,
            GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS,        GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS,
            GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS};

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

        gl.getIntegerv(limitPnames[m_shaderType], &maxBlocks);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");

        if (maxBlocks < 2 + m_numInstances)
            throw tcu::NotSupportedError("Not enough shader storage blocks supported for shader type");
    }
}

void BlockArrayIndexingCase::getShaderSpec(ShaderSpec *spec, int numInstances, int numReads, const int *readIndices,
                                           const RenderContext &renderContext) const
{
    const int binding         = 2;
    const char *blockName     = "Block";
    const char *instanceName  = "block";
    const char *indicesPrefix = "index";
    const char *resultPrefix  = "result";
    const char *interfaceName = m_blockType == BLOCKTYPE_UNIFORM ? "uniform" : "buffer";
    const char *layout        = m_blockType == BLOCKTYPE_UNIFORM ? "std140" : "std430";
    const bool supportsES32 =
        contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) ||
        hasExtension(renderContext.getFunctions(), glu::ApiType::core(4, 5), "GL_ARB_ES3_2_compatibility");
    std::ostringstream global;
    std::ostringstream code;

    if (!supportsES32 && m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL &&
        m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION)
        global << "#extension GL_EXT_gpu_shader5 : require\n";

    if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION)
        global << "const highp int indexBase = 1;\n";

    global << "layout(" << layout << ", binding = " << binding << ") " << interfaceName << " " << blockName
           << "\n"
              "{\n"
              "    uint value;\n"
              "} "
           << instanceName << "[" << numInstances << "];\n";

    if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
    {
        for (int readNdx = 0; readNdx < numReads; readNdx++)
        {
            const string varName = indicesPrefix + de::toString(readNdx);
            spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP)));
        }
    }
    else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
        declareUniformIndexVars(global, indicesPrefix, numReads);

    for (int readNdx = 0; readNdx < numReads; readNdx++)
    {
        const string varName = resultPrefix + de::toString(readNdx);
        spec->outputs.push_back(Symbol(varName, VarType(TYPE_UINT, PRECISION_HIGHP)));
    }

    for (int readNdx = 0; readNdx < numReads; readNdx++)
    {
        code << resultPrefix << readNdx << " = " << instanceName << "[";

        if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL)
            code << readIndices[readNdx];
        else if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION)
            code << "indexBase + " << (readIndices[readNdx] - 1);
        else
            code << indicesPrefix << readNdx;

        code << "].value;\n";
    }

    spec->version            = supportsES32 ? GLSL_VERSION_320_ES : GLSL_VERSION_310_ES;
    spec->globalDeclarations = global.str();
    spec->source             = code.str();
}

BlockArrayIndexingCase::IterateResult BlockArrayIndexingCase::iterate(void)
{
    const int numInvocations = 32;
    const int numInstances   = m_numInstances;
    const int numReads       = 4;
    vector<int> readIndices(numReads);
    vector<uint32_t> inValues(numInstances);
    vector<uint32_t> outValues(numInvocations * numReads);
    ShaderSpec shaderSpec;
    de::Random rnd(deInt32Hash(m_shaderType) ^ deInt32Hash(m_blockType) ^ deInt32Hash(m_indexExprType));

    for (int readNdx = 0; readNdx < numReads; readNdx++)
        readIndices[readNdx] = rnd.getInt(0, numInstances - 1);

    for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
        inValues[instanceNdx] = rnd.getUint32();

    getShaderSpec(&shaderSpec, numInstances, numReads, &readIndices[0], m_context.getRenderContext());

    {
        const RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl       = renderCtx.getFunctions();
        const int baseBinding          = 2;
        const BufferVector buffers(renderCtx, numInstances);
        const uint32_t bufTarget = m_blockType == BLOCKTYPE_BUFFER ? GL_SHADER_STORAGE_BUFFER : GL_UNIFORM_BUFFER;
        ShaderExecutorPtr shaderExecutor(createExecutor(renderCtx, m_shaderType, shaderSpec));
        vector<int> expandedIndices;
        vector<void *> inputs;
        vector<void *> outputs;

        m_testCtx.getLog() << *shaderExecutor;

        if (!shaderExecutor->isOk())
            TCU_FAIL("Compile failed");

        shaderExecutor->useProgram();

        for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
        {
            gl.bindBuffer(bufTarget, buffers[instanceNdx]);
            gl.bufferData(bufTarget, (glw::GLsizeiptr)sizeof(uint32_t), &inValues[instanceNdx], GL_STATIC_DRAW);
            gl.bindBufferBase(bufTarget, baseBinding + instanceNdx, buffers[instanceNdx]);
        }

        if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
        {
            expandedIndices.resize(numInvocations * readIndices.size());

            for (int readNdx = 0; readNdx < numReads; readNdx++)
            {
                int *dst = &expandedIndices[numInvocations * readNdx];
                std::fill(dst, dst + numInvocations, readIndices[readNdx]);
            }

            for (int readNdx = 0; readNdx < numReads; readNdx++)
                inputs.push_back(&expandedIndices[readNdx * numInvocations]);
        }
        else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
            uploadUniformIndices(gl, shaderExecutor->getProgram(), "index", numReads, &readIndices[0]);

        for (int readNdx = 0; readNdx < numReads; readNdx++)
            outputs.push_back(&outValues[readNdx * numInvocations]);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

        shaderExecutor->execute(numInvocations, inputs.empty() ? DE_NULL : &inputs[0], &outputs[0]);
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
    {
        for (int readNdx = 0; readNdx < numReads; readNdx++)
        {
            const uint32_t refValue = inValues[readIndices[readNdx]];
            const uint32_t resValue = outValues[readNdx * numInvocations + invocationNdx];

            if (refValue != resValue)
            {
                m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx << ", read "
                                   << readNdx << ": expected " << tcu::toHex(refValue) << ", got "
                                   << tcu::toHex(resValue) << TestLog::EndMessage;

                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result value");
            }
        }
    }

    return STOP;
}

class AtomicCounterIndexingCase : public TestCase
{
public:
    AtomicCounterIndexingCase(Context &context, const char *name, const char *description, IndexExprType indexExprType,
                              ShaderType shaderType);
    ~AtomicCounterIndexingCase(void);

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

private:
    AtomicCounterIndexingCase(const AtomicCounterIndexingCase &);
    AtomicCounterIndexingCase &operator=(const AtomicCounterIndexingCase &);

    void getShaderSpec(ShaderSpec *spec, int numCounters, int numOps, const int *opIndices,
                       const RenderContext &renderContext) const;

    const IndexExprType m_indexExprType;
    const glu::ShaderType m_shaderType;
    int32_t m_numCounters;
};

AtomicCounterIndexingCase::AtomicCounterIndexingCase(Context &context, const char *name, const char *description,
                                                     IndexExprType indexExprType, ShaderType shaderType)
    : TestCase(context, name, description)
    , m_indexExprType(indexExprType)
    , m_shaderType(shaderType)
    , m_numCounters(0)
{
}

AtomicCounterIndexingCase::~AtomicCounterIndexingCase(void)
{
}

uint32_t getMaxAtomicCounterEnum(glu::ShaderType type)
{
    switch (type)
    {
    case glu::SHADERTYPE_VERTEX:
        return GL_MAX_VERTEX_ATOMIC_COUNTERS;
    case glu::SHADERTYPE_FRAGMENT:
        return GL_MAX_FRAGMENT_ATOMIC_COUNTERS;
    case glu::SHADERTYPE_GEOMETRY:
        return GL_MAX_GEOMETRY_ATOMIC_COUNTERS;
    case glu::SHADERTYPE_COMPUTE:
        return GL_MAX_COMPUTE_ATOMIC_COUNTERS;
    case glu::SHADERTYPE_TESSELLATION_CONTROL:
        return GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS;
    case glu::SHADERTYPE_TESSELLATION_EVALUATION:
        return GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS;

    default:
        DE_FATAL("Unknown shader type");
        return -1;
    }
}

void AtomicCounterIndexingCase::init(void)
{
    const bool supportsES32 = contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
                              hasExtension(m_context.getRenderContext().getFunctions(), glu::ApiType::core(4, 5),
                                           "GL_ARB_ES3_2_compatibility");

    if (!supportsES32)
    {
        if (m_shaderType == SHADERTYPE_GEOMETRY)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"),
                                "GL_EXT_geometry_shader extension is required to run geometry shader tests.");

        if (m_shaderType == SHADERTYPE_TESSELLATION_CONTROL || m_shaderType == SHADERTYPE_TESSELLATION_EVALUATION)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"),
                                "GL_EXT_tessellation_shader extension is required to run tessellation shader tests.");

        if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION)
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"),
                                "GL_EXT_gpu_shader5 extension is required for dynamic indexing of atomic counters.");
    }

    {
        m_context.getRenderContext().getFunctions().getIntegerv(getMaxAtomicCounterEnum(m_shaderType), &m_numCounters);

        if (m_numCounters < 1)
        {
            const string message =
                "Atomic counters not supported in " + string(glu::getShaderTypeName(m_shaderType)) + " shader";
            TCU_THROW(NotSupportedError, message.c_str());
        }
    }
}

void AtomicCounterIndexingCase::getShaderSpec(ShaderSpec *spec, int numCounters, int numOps, const int *opIndices,
                                              const RenderContext &renderContext) const
{
    const char *indicesPrefix = "index";
    const char *resultPrefix  = "result";
    const bool supportsES32 =
        contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) ||
        hasExtension(renderContext.getFunctions(), glu::ApiType::core(4, 5), "GL_ARB_ES3_2_compatibility");
    std::ostringstream global;
    std::ostringstream code;

    if (!supportsES32 && m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL &&
        m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION)
        global << "#extension GL_EXT_gpu_shader5 : require\n";

    if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION)
        global << "const highp int indexBase = 1;\n";

    global << "layout(binding = 0) uniform atomic_uint counter[" << numCounters << "];\n";

    if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
    {
        for (int opNdx = 0; opNdx < numOps; opNdx++)
        {
            const string varName = indicesPrefix + de::toString(opNdx);
            spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP)));
        }
    }
    else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
        declareUniformIndexVars(global, indicesPrefix, numOps);

    for (int opNdx = 0; opNdx < numOps; opNdx++)
    {
        const string varName = resultPrefix + de::toString(opNdx);
        spec->outputs.push_back(Symbol(varName, VarType(TYPE_UINT, PRECISION_HIGHP)));
    }

    for (int opNdx = 0; opNdx < numOps; opNdx++)
    {
        code << resultPrefix << opNdx << " = atomicCounterIncrement(counter[";

        if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL)
            code << opIndices[opNdx];
        else if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION)
            code << "indexBase + " << (opIndices[opNdx] - 1);
        else
            code << indicesPrefix << opNdx;

        code << "]);\n";
    }

    spec->version            = supportsES32 ? GLSL_VERSION_320_ES : GLSL_VERSION_310_ES;
    spec->globalDeclarations = global.str();
    spec->source             = code.str();
}

AtomicCounterIndexingCase::IterateResult AtomicCounterIndexingCase::iterate(void)
{
    const RenderContext &renderCtx = m_context.getRenderContext();
    const glw::Functions &gl       = renderCtx.getFunctions();
    const Buffer counterBuffer(renderCtx);

    const int numInvocations = 32;
    const int numOps         = 4;
    vector<int> opIndices(numOps);
    vector<uint32_t> outValues(numInvocations * numOps);
    ShaderSpec shaderSpec;
    de::Random rnd(deInt32Hash(m_shaderType) ^ deInt32Hash(m_indexExprType));

    for (int opNdx = 0; opNdx < numOps; opNdx++)
        opIndices[opNdx] = rnd.getInt(0, numOps - 1);

    getShaderSpec(&shaderSpec, m_numCounters, numOps, &opIndices[0], m_context.getRenderContext());

    {
        const BufferVector buffers(renderCtx, m_numCounters);
        ShaderExecutorPtr shaderExecutor(createExecutor(renderCtx, m_shaderType, shaderSpec));
        vector<int> expandedIndices;
        vector<void *> inputs;
        vector<void *> outputs;

        m_testCtx.getLog() << *shaderExecutor;

        if (!shaderExecutor->isOk())
            TCU_FAIL("Compile failed");

        {
            const int bufSize = getProgramResourceInt(gl, shaderExecutor->getProgram(), GL_ATOMIC_COUNTER_BUFFER, 0,
                                                      GL_BUFFER_DATA_SIZE);
            const int maxNdx  = maxElement(opIndices);
            std::vector<uint8_t> emptyData(m_numCounters * 4, 0);

            if (bufSize < (maxNdx + 1) * 4)
                TCU_FAIL((string("GL reported invalid buffer size " + de::toString(bufSize)).c_str()));

            gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, *counterBuffer);
            gl.bufferData(GL_ATOMIC_COUNTER_BUFFER, (glw::GLsizeiptr)emptyData.size(), &emptyData[0], GL_STATIC_DRAW);
            gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, *counterBuffer);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Atomic counter buffer initialization failed");
        }

        shaderExecutor->useProgram();

        if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM)
        {
            expandedIndices.resize(numInvocations * opIndices.size());

            for (int opNdx = 0; opNdx < numOps; opNdx++)
            {
                int *dst = &expandedIndices[numInvocations * opNdx];
                std::fill(dst, dst + numInvocations, opIndices[opNdx]);
            }

            for (int opNdx = 0; opNdx < numOps; opNdx++)
                inputs.push_back(&expandedIndices[opNdx * numInvocations]);
        }
        else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM)
            uploadUniformIndices(gl, shaderExecutor->getProgram(), "index", numOps, &opIndices[0]);

        for (int opNdx = 0; opNdx < numOps; opNdx++)
            outputs.push_back(&outValues[opNdx * numInvocations]);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

        shaderExecutor->execute(numInvocations, inputs.empty() ? DE_NULL : &inputs[0], &outputs[0]);
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    {
        vector<int> numHits(m_numCounters, 0); // Number of hits per counter.
        vector<uint32_t> counterValues(m_numCounters);
        vector<vector<bool>> counterMasks(m_numCounters);

        for (int opNdx = 0; opNdx < numOps; opNdx++)
            numHits[opIndices[opNdx]] += 1;

        // Read counter values
        {
            const void *mapPtr = DE_NULL;

            try
            {
                mapPtr = gl.mapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, m_numCounters * 4, GL_MAP_READ_BIT);
                GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER)");
                TCU_CHECK(mapPtr);
                std::copy((const uint32_t *)mapPtr, (const uint32_t *)mapPtr + m_numCounters, &counterValues[0]);
                gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
            }
            catch (...)
            {
                if (mapPtr)
                    gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
                throw;
            }
        }

        // Verify counter values
        for (int counterNdx = 0; counterNdx < m_numCounters; counterNdx++)
        {
            const uint32_t refCount = (uint32_t)(numHits[counterNdx] * numInvocations);
            const uint32_t resCount = counterValues[counterNdx];

            if (refCount != resCount)
            {
                m_testCtx.getLog() << TestLog::Message << "ERROR: atomic counter " << counterNdx << " has value "
                                   << resCount << ", expected " << refCount << TestLog::EndMessage;

                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid atomic counter value");
            }
        }

        // Allocate bitmasks - one bit per each valid result value
        for (int counterNdx = 0; counterNdx < m_numCounters; counterNdx++)
        {
            const int counterValue = numHits[counterNdx] * numInvocations;
            counterMasks[counterNdx].resize(counterValue, false);
        }

        // Verify result values from shaders
        for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++)
        {
            for (int opNdx = 0; opNdx < numOps; opNdx++)
            {
                const int counterNdx    = opIndices[opNdx];
                const uint32_t resValue = outValues[opNdx * numInvocations + invocationNdx];
                const bool rangeOk      = de::inBounds(resValue, 0u, (uint32_t)counterMasks[counterNdx].size());
                const bool notSeen      = rangeOk && !counterMasks[counterNdx][resValue];
                const bool isOk         = rangeOk && notSeen;

                if (!isOk)
                {
                    m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx << ", op "
                                       << opNdx << ": got invalid result value " << resValue << TestLog::EndMessage;

                    if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result value");
                }
                else
                {
                    // Mark as used - no other invocation should see this value from same counter.
                    counterMasks[counterNdx][resValue] = true;
                }
            }
        }

        if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
        {
            // Consistency check - all masks should be 1 now
            for (int counterNdx = 0; counterNdx < m_numCounters; counterNdx++)
            {
                for (vector<bool>::const_iterator i = counterMasks[counterNdx].begin();
                     i != counterMasks[counterNdx].end(); i++)
                    TCU_CHECK_INTERNAL(*i);
            }
        }
    }

    return STOP;
}

} // namespace

OpaqueTypeIndexingTests::OpaqueTypeIndexingTests(Context &context)
    : TestCaseGroup(context, "opaque_type_indexing", "Opaque Type Indexing Tests")
{
}

OpaqueTypeIndexingTests::~OpaqueTypeIndexingTests(void)
{
}

void OpaqueTypeIndexingTests::init(void)
{
    static const struct
    {
        IndexExprType type;
        const char *name;
        const char *description;
    } indexingTypes[] = {
        {INDEX_EXPR_TYPE_CONST_LITERAL, "const_literal", "Indexing by constant literal"},
        {INDEX_EXPR_TYPE_CONST_EXPRESSION, "const_expression", "Indexing by constant expression"},
        {INDEX_EXPR_TYPE_UNIFORM, "uniform", "Indexing by uniform value"},
        {INDEX_EXPR_TYPE_DYNAMIC_UNIFORM, "dynamically_uniform", "Indexing by dynamically uniform expression"}};

    static const struct
    {
        ShaderType type;
        const char *name;
    } shaderTypes[] = {{SHADERTYPE_VERTEX, "vertex"},
                       {SHADERTYPE_FRAGMENT, "fragment"},
                       {SHADERTYPE_COMPUTE, "compute"},
                       {SHADERTYPE_GEOMETRY, "geometry"},
                       {SHADERTYPE_TESSELLATION_CONTROL, "tessellation_control"},
                       {SHADERTYPE_TESSELLATION_EVALUATION, "tessellation_evaluation"}};

    // .sampler
    {
        static const DataType samplerTypes[] = {
            // \note 1D images will be added by a later extension.
            //            TYPE_SAMPLER_1D,
            TYPE_SAMPLER_2D, TYPE_SAMPLER_CUBE, TYPE_SAMPLER_2D_ARRAY, TYPE_SAMPLER_3D,
            //            TYPE_SAMPLER_1D_SHADOW,
            TYPE_SAMPLER_2D_SHADOW, TYPE_SAMPLER_CUBE_SHADOW, TYPE_SAMPLER_2D_ARRAY_SHADOW,
            //            TYPE_INT_SAMPLER_1D,
            TYPE_INT_SAMPLER_2D, TYPE_INT_SAMPLER_CUBE, TYPE_INT_SAMPLER_2D_ARRAY, TYPE_INT_SAMPLER_3D,
            //            TYPE_UINT_SAMPLER_1D,
            TYPE_UINT_SAMPLER_2D, TYPE_UINT_SAMPLER_CUBE, TYPE_UINT_SAMPLER_2D_ARRAY, TYPE_UINT_SAMPLER_3D,
            TYPE_SAMPLER_CUBE_ARRAY, TYPE_SAMPLER_CUBE_ARRAY_SHADOW, TYPE_INT_SAMPLER_CUBE_ARRAY,
            TYPE_UINT_SAMPLER_CUBE_ARRAY};

        tcu::TestCaseGroup *const samplerGroup =
            new tcu::TestCaseGroup(m_testCtx, "sampler", "Sampler Array Indexing Tests");
        addChild(samplerGroup);

        for (int indexTypeNdx = 0; indexTypeNdx < DE_LENGTH_OF_ARRAY(indexingTypes); indexTypeNdx++)
        {
            const IndexExprType indexExprType    = indexingTypes[indexTypeNdx].type;
            tcu::TestCaseGroup *const indexGroup = new tcu::TestCaseGroup(m_testCtx, indexingTypes[indexTypeNdx].name,
                                                                          indexingTypes[indexTypeNdx].description);
            samplerGroup->addChild(indexGroup);

            for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++)
            {
                const ShaderType shaderType = shaderTypes[shaderTypeNdx].type;
                tcu::TestCaseGroup *const shaderGroup =
                    new tcu::TestCaseGroup(m_testCtx, shaderTypes[shaderTypeNdx].name, "");
                indexGroup->addChild(shaderGroup);

                for (int samplerTypeNdx = 0; samplerTypeNdx < DE_LENGTH_OF_ARRAY(samplerTypes); samplerTypeNdx++)
                {
                    const DataType samplerType = samplerTypes[samplerTypeNdx];
                    const char *samplerName    = getDataTypeName(samplerType);
                    const string caseName      = de::toLower(samplerName);

                    shaderGroup->addChild(new SamplerIndexingCase(m_context, caseName.c_str(), "", shaderType,
                                                                  samplerType, indexExprType));
                }
            }
        }
    }

    // .ubo / .ssbo / .atomic_counter
    {
        tcu::TestCaseGroup *const uboGroup =
            new tcu::TestCaseGroup(m_testCtx, "ubo", "Uniform Block Instance Array Indexing Tests");
        tcu::TestCaseGroup *const ssboGroup =
            new tcu::TestCaseGroup(m_testCtx, "ssbo", "Buffer Block Instance Array Indexing Tests");
        tcu::TestCaseGroup *const acGroup =
            new tcu::TestCaseGroup(m_testCtx, "atomic_counter", "Atomic Counter Array Indexing Tests");
        addChild(uboGroup);
        addChild(ssboGroup);
        addChild(acGroup);

        for (int indexTypeNdx = 0; indexTypeNdx < DE_LENGTH_OF_ARRAY(indexingTypes); indexTypeNdx++)
        {
            const IndexExprType indexExprType = indexingTypes[indexTypeNdx].type;
            const char *indexExprName         = indexingTypes[indexTypeNdx].name;
            const char *indexExprDesc         = indexingTypes[indexTypeNdx].description;

            for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++)
            {
                const ShaderType shaderType = shaderTypes[shaderTypeNdx].type;
                const string name           = string(indexExprName) + "_" + shaderTypes[shaderTypeNdx].name;

                uboGroup->addChild(new BlockArrayIndexingCase(m_context, name.c_str(), indexExprDesc,
                                                              BlockArrayIndexingCase::BLOCKTYPE_UNIFORM, indexExprType,
                                                              shaderType));
                acGroup->addChild(
                    new AtomicCounterIndexingCase(m_context, name.c_str(), indexExprDesc, indexExprType, shaderType));

                if (indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL || indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION)
                    ssboGroup->addChild(new BlockArrayIndexingCase(m_context, name.c_str(), indexExprDesc,
                                                                   BlockArrayIndexingCase::BLOCKTYPE_BUFFER,
                                                                   indexExprType, shaderType));
            }
        }
    }
}

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