/*-------------------------------------------------------------------------
 * 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 Explicit uniform location tests
 *//*--------------------------------------------------------------------*/

#include "es31fUniformLocationTests.hpp"

#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuCommandLine.hpp"

#include "glsShaderLibrary.hpp"
#include "glsTextureTestUtil.hpp"

#include "gluShaderProgram.hpp"
#include "gluTexture.hpp"
#include "gluPixelTransfer.hpp"
#include "gluVarType.hpp"
#include "gluVarTypeUtil.hpp"

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

#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include "deString.h"
#include "deRandom.hpp"
#include "deInt32.h"

#include <set>
#include <map>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using de::UniquePtr;
using glu::VarType;
using std::map;
using std::string;
using std::vector;

struct UniformInfo
{
    enum ShaderStage
    {
        SHADERSTAGE_NONE     = 0,
        SHADERSTAGE_VERTEX   = (1 << 0),
        SHADERSTAGE_FRAGMENT = (1 << 1),
        SHADERSTAGE_BOTH     = (SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT),
    };

    VarType type;
    ShaderStage declareLocation; // support declarations with/without layout qualifiers, needed for linkage testing
    ShaderStage layoutLocation;
    ShaderStage checkLocation;
    int location; // -1 for unset

    UniformInfo(VarType type_, ShaderStage declareLocation_, ShaderStage layoutLocation_, ShaderStage checkLocation_,
                int location_ = -1)
        : type(type_)
        , declareLocation(declareLocation_)
        , layoutLocation(layoutLocation_)
        , checkLocation(checkLocation_)
        , location(location_)
    {
    }
};

class UniformLocationCase : public tcu::TestCase
{
public:
    UniformLocationCase(tcu::TestContext &context, glu::RenderContext &renderContext, const char *name,
                        const char *desc, const vector<UniformInfo> &uniformInfo);
    virtual ~UniformLocationCase(void)
    {
    }

    virtual IterateResult iterate(void);

protected:
    IterateResult run(const vector<UniformInfo> &uniformList);
    static glu::ProgramSources genShaderSources(const vector<UniformInfo> &uniformList);
    bool verifyLocations(const glu::ShaderProgram &program, const vector<UniformInfo> &uniformList);
    void render(const glu::ShaderProgram &program, const vector<UniformInfo> &uniformList);
    static bool verifyResult(const tcu::ConstPixelBufferAccess &access);

    static float getExpectedValue(glu::DataType type, int id, const char *name);

    de::MovePtr<glu::Texture2D> createTexture(glu::DataType samplerType, float redChannelValue, int binding);

    glu::RenderContext &m_renderCtx;

    const vector<UniformInfo> m_uniformInfo;

    enum
    {
        RENDER_SIZE = 16
    };
};

string getUniformName(int ndx, const glu::VarType &type, const glu::TypeComponentVector &path)
{
    std::ostringstream buff;
    buff << "uni" << ndx << glu::TypeAccessFormat(type, path);

    return buff.str();
}

string getFirstComponentName(const glu::VarType &type)
{
    std::ostringstream buff;
    if (glu::isDataTypeVector(type.getBasicType()))
        buff << glu::TypeAccessFormat(type, glu::SubTypeAccess(type).component(0).getPath());
    else if (glu::isDataTypeMatrix(type.getBasicType()))
        buff << glu::TypeAccessFormat(type, glu::SubTypeAccess(type).column(0).component(0).getPath());

    return buff.str();
}

UniformLocationCase::UniformLocationCase(tcu::TestContext &context, glu::RenderContext &renderContext, const char *name,
                                         const char *desc, const vector<UniformInfo> &uniformInfo)
    : TestCase(context, name, desc)
    , m_renderCtx(renderContext)
    , m_uniformInfo(uniformInfo)
{
}

// [from, to]
std::vector<int> shuffledRange(int from, int to, int seed)
{
    const int count = to - from;

    vector<int> retval(count);
    de::Random rng(seed);

    DE_ASSERT(count > 0);

    for (int ndx = 0; ndx < count; ndx++)
        retval[ndx] = ndx + from;

    rng.shuffle(retval.begin(), retval.end());
    return retval;
}

glu::DataType getDataTypeSamplerSampleType(glu::DataType type)
{
    using namespace glu;

    if (type >= TYPE_SAMPLER_1D && type <= TYPE_SAMPLER_3D)
        return TYPE_FLOAT_VEC4;
    else if (type >= TYPE_INT_SAMPLER_1D && type <= TYPE_INT_SAMPLER_3D)
        return TYPE_INT_VEC4;
    else if (type >= TYPE_UINT_SAMPLER_1D && type <= TYPE_UINT_SAMPLER_3D)
        return TYPE_UINT_VEC4;
    else if (type >= TYPE_SAMPLER_1D_SHADOW && type <= TYPE_SAMPLER_2D_ARRAY_SHADOW)
        return TYPE_FLOAT;
    else
        DE_FATAL("Unknown sampler type");

    return TYPE_INVALID;
}

// A (hopefully) unique value for a uniform. For multi-component types creates only one value. Values are in the range [0,1] for floats, [-128, 127] for ints, [0,255] for uints and 0/1 for booleans. Samplers are treated according to the types they return.
float UniformLocationCase::getExpectedValue(glu::DataType type, int id, const char *name)
{
    const uint32_t hash = deStringHash(name) + deInt32Hash(id);

    glu::DataType adjustedType = type;

    if (glu::isDataTypeSampler(type))
        adjustedType = getDataTypeSamplerSampleType(type);

    if (glu::isDataTypeIntOrIVec(adjustedType))
        return float(hash % 128);
    else if (glu::isDataTypeUintOrUVec(adjustedType))
        return float(hash % 255);
    else if (glu::isDataTypeFloatOrVec(adjustedType))
        return float(hash % 255) / 255.0f;
    else if (glu::isDataTypeBoolOrBVec(adjustedType))
        return float(hash % 2);
    else
        DE_FATAL("Unkown primitive type");

    return glu::TYPE_INVALID;
}

UniformLocationCase::IterateResult UniformLocationCase::iterate(void)
{
    return run(m_uniformInfo);
}

UniformLocationCase::IterateResult UniformLocationCase::run(const vector<UniformInfo> &uniformList)
{
    using gls::TextureTestUtil::RandomViewport;

    const glu::ProgramSources sources = genShaderSources(uniformList);
    const glu::ShaderProgram program(m_renderCtx, sources);
    const int baseSeed       = m_testCtx.getCommandLine().getBaseSeed();
    const glw::Functions &gl = m_renderCtx.getFunctions();
    const RandomViewport viewport(m_renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE,
                                  deStringHash(getName()) + baseSeed);

    tcu::Surface rendered(RENDER_SIZE, RENDER_SIZE);

    if (!verifyLocations(program, uniformList))
        return STOP;

    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);

    render(program, uniformList);

    glu::readPixels(m_renderCtx, viewport.x, viewport.y, rendered.getAccess());
    GLU_EXPECT_NO_ERROR(gl.getError(), "error in readPixels");

    if (!verifyResult(rendered.getAccess()))
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader produced incorrect result");
        return STOP;
    }

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

glu::ProgramSources UniformLocationCase::genShaderSources(const vector<UniformInfo> &uniformList)
{
    std::ostringstream vertDecl, vertMain, fragDecl, fragMain;

    vertDecl << "#version 310 es\n"
             << "precision highp float;\n"
             << "precision highp int;\n"
             << "float verify(float val, float ref) { return float(abs(val-ref) < 0.05); }\n\n"
             << "in highp vec4 a_position;\n"
             << "out highp vec4 v_color;\n";
    fragDecl << "#version 310 es\n\n"
             << "precision highp float;\n"
             << "precision highp int;\n"
             << "float verify(float val, float ref) { return float(abs(val-ref) < 0.05); }\n\n"
             << "in highp vec4 v_color;\n"
             << "layout(location = 0) out mediump vec4 o_color;\n\n";

    vertMain << "void main()\n{\n"
             << "    gl_Position = a_position;\n"
             << "    v_color = vec4(1.0);\n";

    fragMain << "void main()\n{\n"
             << "    o_color = v_color;\n";

    std::set<const glu::StructType *> declaredStructs;

    // Declare uniforms
    for (int uniformNdx = 0; uniformNdx < int(uniformList.size()); uniformNdx++)
    {
        const UniformInfo &uniformInfo = uniformList[uniformNdx];

        const bool declareInVert = (uniformInfo.declareLocation & UniformInfo::SHADERSTAGE_VERTEX) != 0;
        const bool declareInFrag = (uniformInfo.declareLocation & UniformInfo::SHADERSTAGE_FRAGMENT) != 0;
        const bool layoutInVert  = (uniformInfo.layoutLocation & UniformInfo::SHADERSTAGE_VERTEX) != 0;
        const bool layoutInFrag  = (uniformInfo.layoutLocation & UniformInfo::SHADERSTAGE_FRAGMENT) != 0;
        const bool checkInVert   = (uniformInfo.checkLocation & UniformInfo::SHADERSTAGE_VERTEX) != 0;
        const bool checkInFrag   = (uniformInfo.checkLocation & UniformInfo::SHADERSTAGE_FRAGMENT) != 0;

        const string layout =
            uniformInfo.location >= 0 ? "layout(location = " + de::toString(uniformInfo.location) + ") " : "";
        const string uniName = "uni" + de::toString(uniformNdx);

        int location     = uniformInfo.location;
        int subTypeIndex = 0;

        DE_ASSERT((declareInVert && layoutInVert) || !layoutInVert); // Cannot have layout without declaration
        DE_ASSERT((declareInFrag && layoutInFrag) || !layoutInFrag);
        DE_ASSERT(location < 0 || (layoutInVert || layoutInFrag)); // Cannot have location without layout

        // struct definitions
        if (uniformInfo.type.isStructType())
        {
            const glu::StructType *const structType = uniformInfo.type.getStructPtr();
            if (!declaredStructs.count(structType))
            {
                if (declareInVert)
                    vertDecl << glu::declare(structType, 0) << ";\n";

                if (declareInFrag)
                    fragDecl << glu::declare(structType, 0) << ";\n";

                declaredStructs.insert(structType);
            }
        }

        if (declareInVert)
            vertDecl << "uniform " << (layoutInVert ? layout : "") << glu::declare(uniformInfo.type, uniName) << ";\n";

        if (declareInFrag)
            fragDecl << "uniform " << (layoutInFrag ? layout : "") << glu::declare(uniformInfo.type, uniName) << ";\n";

        // Anything that needs to be done for each enclosed primitive type
        for (glu::BasicTypeIterator subTypeIter = glu::BasicTypeIterator::begin(&uniformInfo.type);
             subTypeIter != glu::BasicTypeIterator::end(&uniformInfo.type); subTypeIter++, subTypeIndex++)
        {
            const glu::VarType subType     = glu::getVarType(uniformInfo.type, subTypeIter.getPath());
            const glu::DataType scalarType = glu::getDataTypeScalarType(subType.getBasicType());
            const char *const typeName     = glu::getDataTypeName(scalarType);
            const string expectValue       = de::floatToString(
                getExpectedValue(scalarType, location >= 0 ? location + subTypeIndex : -1, typeName), 3);

            if (glu::isDataTypeSampler(scalarType))
            {
                if (checkInVert)
                    vertMain << "    v_color.rgb *= verify(float( texture(" << uniName
                             << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath()) << ", vec2(0.5)).r), "
                             << expectValue << ");\n";
                if (checkInFrag)
                    fragMain << "    o_color.rgb *= verify(float( texture(" << uniName
                             << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath()) << ", vec2(0.5)).r), "
                             << expectValue << ");\n";
            }
            else
            {
                if (checkInVert)
                    vertMain << "    v_color.rgb *= verify(float(" << uniName
                             << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath())
                             << getFirstComponentName(subType) << "), " << expectValue << ");\n";
                if (checkInFrag)
                    fragMain << "    o_color.rgb *= verify(float(" << uniName
                             << glu::TypeAccessFormat(uniformInfo.type, subTypeIter.getPath())
                             << getFirstComponentName(subType) << "), " << expectValue << ");\n";
            }
        }
    }

    vertMain << "}\n";
    fragMain << "}\n";

    return glu::makeVtxFragSources(vertDecl.str() + vertMain.str(), fragDecl.str() + fragMain.str());
}

bool UniformLocationCase::verifyLocations(const glu::ShaderProgram &program, const vector<UniformInfo> &uniformList)
{
    using tcu::TestLog;

    const glw::Functions &gl = m_renderCtx.getFunctions();
    const bool vertexOk      = program.getShaderInfo(glu::SHADERTYPE_VERTEX).compileOk;
    const bool fragmentOk    = program.getShaderInfo(glu::SHADERTYPE_FRAGMENT).compileOk;
    const bool linkOk        = program.getProgramInfo().linkOk;
    const uint32_t programID = program.getProgram();

    TestLog &log = m_testCtx.getLog();
    std::set<int> usedLocations;

    log << program;

    if (!vertexOk || !fragmentOk || !linkOk)
    {
        log << TestLog::Message << "ERROR: shader failed to compile/link" << TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader failed to compile/link");
        return false;
    }

    for (int uniformNdx = 0; uniformNdx < int(uniformList.size()); uniformNdx++)
    {
        const UniformInfo &uniformInfo = uniformList[uniformNdx];
        int subTypeIndex               = 0;

        for (glu::BasicTypeIterator subTypeIter = glu::BasicTypeIterator::begin(&uniformInfo.type);
             subTypeIter != glu::BasicTypeIterator::end(&uniformInfo.type); subTypeIter++, subTypeIndex++)
        {
            const string name   = getUniformName(uniformNdx, uniformInfo.type, subTypeIter.getPath());
            const int gotLoc    = gl.getUniformLocation(programID, name.c_str());
            const int expectLoc = uniformInfo.location >= 0 ? uniformInfo.location + subTypeIndex : -1;

            if (expectLoc >= 0)
            {
                if (uniformInfo.checkLocation == 0 && gotLoc == -1)
                    continue;

                if (gotLoc != expectLoc)
                {
                    log << TestLog::Message << "ERROR: found uniform " << name << " in location " << gotLoc
                        << " when it should have been in " << expectLoc << TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Incorrect uniform location");
                    return false;
                }

                if (usedLocations.find(expectLoc) != usedLocations.end())
                {
                    log << TestLog::Message << "ERROR: expected uniform " << name << " in location " << gotLoc
                        << " but it has already been used" << TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Overlapping uniform location");
                    return false;
                }

                usedLocations.insert(expectLoc);
            }
            else if (gotLoc >= 0)
            {
                if (usedLocations.count(gotLoc))
                {
                    log << TestLog::Message << "ERROR: found uniform " << name << " in location " << gotLoc
                        << " which has already been used" << TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Overlapping uniform location");
                    return false;
                }

                usedLocations.insert(gotLoc);
            }
        }
    }

    return true;
}

// Check that shader output is white (or very close to it)
bool UniformLocationCase::verifyResult(const tcu::ConstPixelBufferAccess &access)
{
    using tcu::Vec4;

    const Vec4 threshold(0.1f, 0.1f, 0.1f, 0.1f);
    const Vec4 reference(1.0f, 1.0f, 1.0f, 1.0f);

    for (int y = 0; y < access.getHeight(); y++)
    {
        for (int x = 0; x < access.getWidth(); x++)
        {
            const Vec4 diff = abs(access.getPixel(x, y) - reference);

            if (!boolAll(lessThanEqual(diff, threshold)))
                return false;
        }
    }

    return true;
}

// get a 4 channel 8 bits each texture format that is usable by the given sampler type
uint32_t getTextureFormat(glu::DataType samplerType)
{
    using namespace glu;

    switch (samplerType)
    {
    case TYPE_SAMPLER_1D:
    case TYPE_SAMPLER_2D:
    case TYPE_SAMPLER_CUBE:
    case TYPE_SAMPLER_2D_ARRAY:
    case TYPE_SAMPLER_3D:
        return GL_RGBA8;

    case TYPE_INT_SAMPLER_1D:
    case TYPE_INT_SAMPLER_2D:
    case TYPE_INT_SAMPLER_CUBE:
    case TYPE_INT_SAMPLER_2D_ARRAY:
    case TYPE_INT_SAMPLER_3D:
        return GL_RGBA8I;

    case TYPE_UINT_SAMPLER_1D:
    case TYPE_UINT_SAMPLER_2D:
    case TYPE_UINT_SAMPLER_CUBE:
    case TYPE_UINT_SAMPLER_2D_ARRAY:
    case TYPE_UINT_SAMPLER_3D:
        return GL_RGBA8UI;

    default:
        DE_FATAL("Unsupported (sampler) type");
        return 0;
    }
}

// create a texture suitable for sampling by the given sampler type and bind it
de::MovePtr<glu::Texture2D> UniformLocationCase::createTexture(glu::DataType samplerType, float redChannelValue,
                                                               int binding)
{
    using namespace glu;

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

    const uint32_t format = getTextureFormat(samplerType);
    de::MovePtr<Texture2D> tex;

    tex = de::MovePtr<Texture2D>(new Texture2D(m_renderCtx, format, 16, 16));

    tex->getRefTexture().allocLevel(0);

    if (format == GL_RGBA8I || format == GL_RGBA8UI)
        tcu::clear(tex->getRefTexture().getLevel(0), tcu::IVec4(int(redChannelValue), 0, 0, 0));
    else
        tcu::clear(tex->getRefTexture().getLevel(0), tcu::Vec4(redChannelValue, 0.0f, 0.0f, 1.0f));

    gl.activeTexture(GL_TEXTURE0 + binding);
    tex->upload();

    gl.bindTexture(GL_TEXTURE_2D, tex->getGLTexture());
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    GLU_EXPECT_NO_ERROR(gl.getError(), "UniformLocationCase: texture upload");

    return tex;
}

void UniformLocationCase::render(const glu::ShaderProgram &program, const vector<UniformInfo> &uniformList)
{
    using de::MovePtr;
    using glu::Texture2D;
    typedef vector<Texture2D *> TextureList;

    const glw::Functions &gl = m_renderCtx.getFunctions();
    const uint32_t programID = program.getProgram();
    const int32_t posLoc     = gl.getAttribLocation(programID, "a_position");

    // Vertex data.
    const float position[]   = {-1.0f, -1.0f, 0.1f, 1.0f, -1.0f, 1.0f, 0.1f, 1.0f,
                                1.0f,  -1.0f, 0.1f, 1.0f, 1.0f,  1.0f, 0.1f, 1.0f};
    const void *positionPtr  = &position[0];
    const uint16_t indices[] = {0, 1, 2, 2, 1, 3};
    const void *indicesPtr   = &indices[0];

    // some buffers to feed to the GPU, only the first element is relevant since the others are never verified
    float floatBuf[16]  = {0.0f};
    int32_t intBuf[4]   = {0};
    uint32_t uintBuf[4] = {0};
    uint32_t vao        = 0;
    uint32_t attribVbo  = 0;
    uint32_t elementVbo = 0;

    TextureList texList;

    bool isGL45 = glu::contextSupports(m_renderCtx.getType(), glu::ApiType::core(4, 5));
    if (isGL45)
    {
        gl.genVertexArrays(1, &vao);
        gl.bindVertexArray(vao);

        gl.genBuffers(1, &attribVbo);
        gl.genBuffers(1, &elementVbo);

        gl.bindBuffer(GL_ARRAY_BUFFER, attribVbo);
        gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(DE_LENGTH_OF_ARRAY(position) * sizeof(float)), position,
                      GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "UniformLocationCase::render");

        gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementVbo);
        gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(DE_LENGTH_OF_ARRAY(indices) * sizeof(uint16_t)),
                      indices, GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "UniformLocationCase::render");

        positionPtr = 0;
        indicesPtr  = 0;
    }

    TCU_CHECK(posLoc >= 0);
    gl.useProgram(programID);

    try
    {

        // Set uniforms
        for (unsigned int uniformNdx = 0; uniformNdx < uniformList.size(); uniformNdx++)
        {
            const UniformInfo &uniformInfo = uniformList[uniformNdx];
            int expectedLocation           = uniformInfo.location;

            for (glu::BasicTypeIterator subTypeIter = glu::BasicTypeIterator::begin(&uniformInfo.type);
                 subTypeIter != glu::BasicTypeIterator::end(&uniformInfo.type); subTypeIter++)
            {
                const glu::VarType type        = glu::getVarType(uniformInfo.type, subTypeIter.getPath());
                const string name              = getUniformName(uniformNdx, uniformInfo.type, subTypeIter.getPath());
                const int gotLoc               = gl.getUniformLocation(programID, name.c_str());
                const glu::DataType scalarType = glu::getDataTypeScalarType(type.getBasicType());
                const char *const typeName     = glu::getDataTypeName(scalarType);
                const float expectedValue      = getExpectedValue(scalarType, expectedLocation, typeName);

                if (glu::isDataTypeSampler(scalarType))
                {
                    const int binding = (int)texList.size();

                    texList.push_back(createTexture(scalarType, expectedValue, binding).release());
                    gl.uniform1i(gotLoc, binding);
                }
                else if (gotLoc >= 0)
                {
                    floatBuf[0] = expectedValue;
                    intBuf[0]   = int(expectedValue);
                    uintBuf[0]  = uint32_t(expectedValue);

                    m_testCtx.getLog() << tcu::TestLog::Message << "Set uniform " << name << " in location " << gotLoc
                                       << " to " << expectedValue << tcu::TestLog::EndMessage;

                    switch (type.getBasicType())
                    {
                    case glu::TYPE_FLOAT:
                        gl.uniform1fv(gotLoc, 1, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_VEC2:
                        gl.uniform2fv(gotLoc, 1, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_VEC3:
                        gl.uniform3fv(gotLoc, 1, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_VEC4:
                        gl.uniform4fv(gotLoc, 1, floatBuf);
                        break;

                    case glu::TYPE_INT:
                        gl.uniform1iv(gotLoc, 1, intBuf);
                        break;
                    case glu::TYPE_INT_VEC2:
                        gl.uniform2iv(gotLoc, 1, intBuf);
                        break;
                    case glu::TYPE_INT_VEC3:
                        gl.uniform3iv(gotLoc, 1, intBuf);
                        break;
                    case glu::TYPE_INT_VEC4:
                        gl.uniform4iv(gotLoc, 1, intBuf);
                        break;

                    case glu::TYPE_UINT:
                        gl.uniform1uiv(gotLoc, 1, uintBuf);
                        break;
                    case glu::TYPE_UINT_VEC2:
                        gl.uniform2uiv(gotLoc, 1, uintBuf);
                        break;
                    case glu::TYPE_UINT_VEC3:
                        gl.uniform3uiv(gotLoc, 1, uintBuf);
                        break;
                    case glu::TYPE_UINT_VEC4:
                        gl.uniform4uiv(gotLoc, 1, uintBuf);
                        break;

                    case glu::TYPE_BOOL:
                        gl.uniform1iv(gotLoc, 1, intBuf);
                        break;
                    case glu::TYPE_BOOL_VEC2:
                        gl.uniform2iv(gotLoc, 1, intBuf);
                        break;
                    case glu::TYPE_BOOL_VEC3:
                        gl.uniform3iv(gotLoc, 1, intBuf);
                        break;
                    case glu::TYPE_BOOL_VEC4:
                        gl.uniform4iv(gotLoc, 1, intBuf);
                        break;

                    case glu::TYPE_FLOAT_MAT2:
                        gl.uniformMatrix2fv(gotLoc, 1, false, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_MAT2X3:
                        gl.uniformMatrix2x3fv(gotLoc, 1, false, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_MAT2X4:
                        gl.uniformMatrix2x4fv(gotLoc, 1, false, floatBuf);
                        break;

                    case glu::TYPE_FLOAT_MAT3X2:
                        gl.uniformMatrix3x2fv(gotLoc, 1, false, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_MAT3:
                        gl.uniformMatrix3fv(gotLoc, 1, false, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_MAT3X4:
                        gl.uniformMatrix3x4fv(gotLoc, 1, false, floatBuf);
                        break;

                    case glu::TYPE_FLOAT_MAT4X2:
                        gl.uniformMatrix4x2fv(gotLoc, 1, false, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_MAT4X3:
                        gl.uniformMatrix4x3fv(gotLoc, 1, false, floatBuf);
                        break;
                    case glu::TYPE_FLOAT_MAT4:
                        gl.uniformMatrix4fv(gotLoc, 1, false, floatBuf);
                        break;
                    default:
                        DE_ASSERT(false);
                    }
                }

                expectedLocation += expectedLocation >= 0;
            }
        }

        gl.enableVertexAttribArray(posLoc);
        GLU_EXPECT_NO_ERROR(gl.getError(), "error in glEnableVertexAttribArray");
        gl.vertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, positionPtr);
        GLU_EXPECT_NO_ERROR(gl.getError(), "error in glVertexAttribPointer");

        gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_SHORT, indicesPtr);
        GLU_EXPECT_NO_ERROR(gl.getError(), "error in glDrawElements");

        gl.disableVertexAttribArray(posLoc);
        GLU_EXPECT_NO_ERROR(gl.getError(), "error in glDisableVertexAttribArray");

        if (isGL45)
        {
            gl.bindBuffer(GL_ARRAY_BUFFER, 0);
            gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

            gl.deleteBuffers(1, &attribVbo);
            gl.deleteBuffers(1, &elementVbo);
            gl.deleteVertexArrays(1, &vao);
        }
    }
    catch (...)
    {
        for (int i = 0; i < int(texList.size()); i++)
            delete texList[i];

        throw;
    }

    for (int i = 0; i < int(texList.size()); i++)
        delete texList[i];
}

class MaxUniformLocationCase : public UniformLocationCase
{
public:
    MaxUniformLocationCase(tcu::TestContext &context, glu::RenderContext &renderContext, const char *name,
                           const char *desc, const vector<UniformInfo> &uniformInfo);
    virtual ~MaxUniformLocationCase(void)
    {
    }
    virtual IterateResult iterate(void);
};

MaxUniformLocationCase::MaxUniformLocationCase(tcu::TestContext &context, glu::RenderContext &renderContext,
                                               const char *name, const char *desc,
                                               const vector<UniformInfo> &uniformInfo)
    : UniformLocationCase(context, renderContext, name, desc, uniformInfo)
{
    DE_ASSERT(!uniformInfo.empty());
}

UniformLocationCase::IterateResult MaxUniformLocationCase::iterate(void)
{
    int maxLocation                 = 1024;
    vector<UniformInfo> uniformInfo = m_uniformInfo;

    m_renderCtx.getFunctions().getIntegerv(GL_MAX_UNIFORM_LOCATIONS, &maxLocation);

    uniformInfo[0].location = maxLocation - 1;

    return UniformLocationCase::run(uniformInfo);
}

} // namespace

UniformLocationTests::UniformLocationTests(Context &context, bool isGL45)
    : TestCaseGroup(context, "uniform_location", "Explicit uniform locations")
    , m_isGL45(isGL45)
{
}

UniformLocationTests::~UniformLocationTests(void)
{
    for (int i = 0; i < int(structTypes.size()); i++)
        delete structTypes[i];
}

glu::VarType createVarType(glu::DataType type)
{
    return glu::VarType(type, glu::isDataTypeBoolOrBVec(type) ? glu::PRECISION_LAST : glu::PRECISION_HIGHP);
}

void UniformLocationTests::init(void)
{
    using namespace glu;

    const UniformInfo::ShaderStage checkStages[] = {UniformInfo::SHADERSTAGE_VERTEX, UniformInfo::SHADERSTAGE_FRAGMENT};
    const char *stageNames[]                     = {"vertex", "fragment"};
    const int maxLocations                       = 1024;
    const int baseSeed                           = m_context.getTestContext().getCommandLine().getBaseSeed();

    const DataType primitiveTypes[] = {
        TYPE_FLOAT,        TYPE_FLOAT_VEC2,     TYPE_FLOAT_VEC3,      TYPE_FLOAT_VEC4,

        TYPE_INT,          TYPE_INT_VEC2,       TYPE_INT_VEC3,        TYPE_INT_VEC4,

        TYPE_UINT,         TYPE_UINT_VEC2,      TYPE_UINT_VEC3,       TYPE_UINT_VEC4,

        TYPE_BOOL,         TYPE_BOOL_VEC2,      TYPE_BOOL_VEC3,       TYPE_BOOL_VEC4,

        TYPE_FLOAT_MAT2,   TYPE_FLOAT_MAT2X3,   TYPE_FLOAT_MAT2X4,    TYPE_FLOAT_MAT3X2, TYPE_FLOAT_MAT3,
        TYPE_FLOAT_MAT3X4, TYPE_FLOAT_MAT4X2,   TYPE_FLOAT_MAT4X3,    TYPE_FLOAT_MAT4,

        TYPE_SAMPLER_2D,   TYPE_INT_SAMPLER_2D, TYPE_UINT_SAMPLER_2D,
    };

    const int maxPrimitiveTypeNdx = DE_LENGTH_OF_ARRAY(primitiveTypes) - 4;
    DE_ASSERT(primitiveTypes[maxPrimitiveTypeNdx] == TYPE_FLOAT_MAT4);

    // Primitive type cases with trivial linkage
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "basic", "Location specified with use, single shader stage");
        de::Random rng(baseSeed + 0x1001);
        addChild(group);

        for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
        {
            const DataType type = primitiveTypes[primitiveNdx];

            for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
            {
                const string name = string(getDataTypeName(type)) + "_" + stageNames[stageNdx];

                vector<UniformInfo> config;

                UniformInfo uniform(createVarType(type), checkStages[stageNdx], checkStages[stageNdx],
                                    checkStages[stageNdx], rng.getInt(0, maxLocations - 1));

                config.push_back(uniform);
                group->addChild(new UniformLocationCase(m_testCtx, m_context.getRenderContext(), name.c_str(),
                                                        name.c_str(), config));
            }
        }
    }

    // Arrays
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "array", "Array location specified with use, single shader stage");
        de::Random rng(baseSeed + 0x2001);
        addChild(group);

        for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
        {
            const DataType type = primitiveTypes[primitiveNdx];

            for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
            {

                const string name = string(getDataTypeName(type)) + "_" + stageNames[stageNdx];

                vector<UniformInfo> config;

                UniformInfo uniform(VarType(createVarType(type), 8), checkStages[stageNdx], checkStages[stageNdx],
                                    checkStages[stageNdx], rng.getInt(0, maxLocations - 1 - 8));

                config.push_back(uniform);
                group->addChild(new UniformLocationCase(m_testCtx, m_context.getRenderContext(), name.c_str(),
                                                        name.c_str(), config));
            }
        }
    }

    // Nested Arrays
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "nested_array", "Array location specified with use, single shader stage");
        de::Random rng(baseSeed + 0x3001);
        addChild(group);

        for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
        {
            const DataType type = primitiveTypes[primitiveNdx];

            for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
            {
                const string name = string(getDataTypeName(type)) + "_" + stageNames[stageNdx];
                // stay comfortably within minimum max uniform component count (896 in fragment) and sampler count with all types
                const int arraySize = (getDataTypeScalarSize(type) > 4 || isDataTypeSampler(type)) ? 3 : 7;

                vector<UniformInfo> config;

                UniformInfo uniform(VarType(VarType(createVarType(type), arraySize), arraySize), checkStages[stageNdx],
                                    checkStages[stageNdx], checkStages[stageNdx],
                                    rng.getInt(0, maxLocations - 1 - arraySize * arraySize));

                config.push_back(uniform);
                group->addChild(new UniformLocationCase(m_testCtx, m_context.getRenderContext(), name.c_str(),
                                                        name.c_str(), config));
            }
        }
    }

    // Structs
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "struct", "Struct location, random contents & declaration location");
        de::Random rng(baseSeed + 0x4001);
        addChild(group);

        for (int caseNdx = 0; caseNdx < 16; caseNdx++)
        {
            typedef UniformInfo::ShaderStage Stage;

            const string name = "case_" + de::toString(caseNdx);

            const Stage layoutLoc  = Stage(rng.getUint32() & 0x3);
            const Stage declareLoc = Stage((rng.getUint32() & 0x3) | layoutLoc);
            const Stage verifyLoc  = Stage((rng.getUint32() & 0x3) & declareLoc);
            const int location     = layoutLoc ? rng.getInt(0, maxLocations - 1 - 5) : -1;

            StructType *structProto = new StructType("S");

            structTypes.push_back(structProto);

            structProto->addMember("a", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            structProto->addMember("b", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            structProto->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            structProto->addMember("d", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            structProto->addMember("e", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));

            {
                vector<UniformInfo> config;

                config.push_back(UniformInfo(VarType(structProto), declareLoc, layoutLoc, verifyLoc, location));
                group->addChild(new UniformLocationCase(m_testCtx, m_context.getRenderContext(), name.c_str(),
                                                        name.c_str(), config));
            }
        }
    }

    // Nested Structs
    {
        tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(
            m_testCtx, "nested_struct", "Struct location specified with use, single shader stage");
        de::Random rng(baseSeed + 0x5001);

        addChild(group);

        for (int caseNdx = 0; caseNdx < 16; caseNdx++)
        {
            typedef UniformInfo::ShaderStage Stage;

            const string name = "case_" + de::toString(caseNdx);
            const int baseLoc = rng.getInt(0, maxLocations - 1 - 60);

            // Structs need to be added in the order of their declaration
            const Stage layoutLocs[] = {
                Stage(rng.getUint32() & 0x3),
                Stage(rng.getUint32() & 0x3),
                Stage(rng.getUint32() & 0x3),
                Stage(rng.getUint32() & 0x3),
            };

            const uint32_t tempDecl[] = {
                (rng.getUint32() & 0x3) | layoutLocs[0],
                (rng.getUint32() & 0x3) | layoutLocs[1],
                (rng.getUint32() & 0x3) | layoutLocs[2],
                (rng.getUint32() & 0x3) | layoutLocs[3],
            };

            // Component structs need to be declared if anything using them is declared
            const Stage declareLocs[] = {
                Stage(tempDecl[0] | tempDecl[1] | tempDecl[2] | tempDecl[3]),
                Stage(tempDecl[1] | tempDecl[2] | tempDecl[3]),
                Stage(tempDecl[2] | tempDecl[3]),
                Stage(tempDecl[3]),
            };

            const Stage verifyLocs[] = {
                Stage(rng.getUint32() & 0x3 & declareLocs[0]),
                Stage(rng.getUint32() & 0x3 & declareLocs[1]),
                Stage(rng.getUint32() & 0x3 & declareLocs[2]),
                Stage(rng.getUint32() & 0x3 & declareLocs[3]),
            };

            StructType *testTypes[] = {
                new StructType("Type0"),
                new StructType("Type1"),
                new StructType("Type2"),
                new StructType("Type3"),
            };

            structTypes.push_back(testTypes[0]);
            structTypes.push_back(testTypes[1]);
            structTypes.push_back(testTypes[2]);
            structTypes.push_back(testTypes[3]);

            testTypes[0]->addMember("a", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[0]->addMember("b", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[0]->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[0]->addMember("d", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[0]->addMember("e", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));

            testTypes[1]->addMember("a", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[1]->addMember("b", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[1]->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[1]->addMember("d", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));
            testTypes[1]->addMember("e", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));

            testTypes[2]->addMember("a", VarType(testTypes[0]));
            testTypes[2]->addMember("b", VarType(testTypes[1]));
            testTypes[2]->addMember("c", createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]));

            testTypes[3]->addMember("a", VarType(testTypes[2]));

            {
                vector<UniformInfo> config;

                config.push_back(UniformInfo(VarType(testTypes[0]), declareLocs[0], layoutLocs[0], verifyLocs[0],
                                             layoutLocs[0] ? baseLoc : -1));

                config.push_back(UniformInfo(VarType(testTypes[1]), declareLocs[1], layoutLocs[1], verifyLocs[1],
                                             layoutLocs[1] ? baseLoc + 5 : -1));

                config.push_back(UniformInfo(VarType(testTypes[2]), declareLocs[2], layoutLocs[2], verifyLocs[2],
                                             layoutLocs[2] ? baseLoc + 16 : -1));

                config.push_back(UniformInfo(VarType(testTypes[3]), declareLocs[3], layoutLocs[3], verifyLocs[3],
                                             layoutLocs[3] ? baseLoc + 27 : -1));

                group->addChild(new UniformLocationCase(m_testCtx, m_context.getRenderContext(), name.c_str(),
                                                        name.c_str(), config));
            }
        }
    }

    // Min/Max location
    {
        tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(m_testCtx, "min_max", "Maximum & minimum location");

        addChild(group);

        for (int primitiveNdx = 0; primitiveNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); primitiveNdx++)
        {
            const DataType type = primitiveTypes[primitiveNdx];

            for (int stageNdx = 0; stageNdx < DE_LENGTH_OF_ARRAY(checkStages); stageNdx++)
            {
                const string name = string(getDataTypeName(type)) + "_" + stageNames[stageNdx];
                vector<UniformInfo> config;

                config.push_back(UniformInfo(createVarType(type), checkStages[stageNdx], checkStages[stageNdx],
                                             checkStages[stageNdx], 0));

                group->addChild(new UniformLocationCase(m_testCtx, m_context.getRenderContext(),
                                                        (name + "_min").c_str(), (name + "_min").c_str(), config));

                group->addChild(new MaxUniformLocationCase(m_testCtx, m_context.getRenderContext(),
                                                           (name + "_max").c_str(), (name + "_max").c_str(), config));
            }
        }
    }

    // Link
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "link", "Location specified independently from use");
        de::Random rng(baseSeed + 0x82e1);

        addChild(group);

        for (int caseNdx = 0; caseNdx < 10; caseNdx++)
        {
            const string name = "case_" + de::toString(caseNdx);
            vector<UniformInfo> config;

            vector<int> locations = shuffledRange(0, maxLocations, 0x1234 + caseNdx * 100);

            for (int count = 0; count < 32; count++)
            {
                typedef UniformInfo::ShaderStage Stage;

                const Stage layoutLoc  = Stage(rng.getUint32() & 0x3);
                const Stage declareLoc = Stage((rng.getUint32() & 0x3) | layoutLoc);
                const Stage verifyLoc  = Stage((rng.getUint32() & 0x3) & declareLoc);

                const UniformInfo uniform(createVarType(primitiveTypes[rng.getInt(0, maxPrimitiveTypeNdx)]), declareLoc,
                                          layoutLoc, verifyLoc, (layoutLoc != 0) ? locations.back() : -1);

                config.push_back(uniform);
                locations.pop_back();
            }
            group->addChild(
                new UniformLocationCase(m_testCtx, m_context.getRenderContext(), name.c_str(), name.c_str(), config));
        }
    }

    // Negative
    {
        de::MovePtr<tcu::TestCaseGroup> negativeGroup(new tcu::TestCaseGroup(m_testCtx, "negative", "Negative tests"));

        {
            de::MovePtr<tcu::TestCaseGroup> es31Group(
                new tcu::TestCaseGroup(m_testCtx, "es31", "GLSL ES 3.1 Negative tests"));
            gls::ShaderLibrary shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
            const vector<TestNode *> negativeCases = shaderLibrary.loadShaderFile("shaders/es31/uniform_location.test");

            for (int ndx = 0; ndx < int(negativeCases.size()); ndx++)
                es31Group->addChild(negativeCases[ndx]);

            negativeGroup->addChild(es31Group.release());
        }

        // ES only
        if (!m_isGL45)
        {
            de::MovePtr<tcu::TestCaseGroup> es32Group(
                new tcu::TestCaseGroup(m_testCtx, "es32", "GLSL ES 3.2 Negative tests"));
            gls::ShaderLibrary shaderLibrary(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
            const vector<TestNode *> negativeCases = shaderLibrary.loadShaderFile("shaders/es32/uniform_location.test");

            for (int ndx = 0; ndx < int(negativeCases.size()); ndx++)
                es32Group->addChild(negativeCases[ndx]);

            negativeGroup->addChild(es32Group.release());
        }

        addChild(negativeGroup.release());
    }
}

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