/*-------------------------------------------------------------------------
 * 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 Tests for separate shader objects
 *//*--------------------------------------------------------------------*/

#include "es31fSeparateShaderTests.hpp"

#include "deInt32.h"
#include "deString.h"
#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include "deRandom.hpp"
#include "deSTLUtil.hpp"
#include "tcuCommandLine.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuResultCollector.hpp"
#include "tcuRGBA.hpp"
#include "tcuSurface.hpp"
#include "tcuStringTemplate.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluVarType.hpp"
#include "glsShaderLibrary.hpp"
#include "glwFunctions.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

#include <cstdarg>
#include <algorithm>
#include <map>
#include <sstream>
#include <string>
#include <set>
#include <vector>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using de::MovePtr;
using de::Random;
using de::UniquePtr;
using glu::CallLogWrapper;
using glu::DataType;
using glu::FragmentSource;
using glu::Precision;
using glu::Program;
using glu::ProgramPipeline;
using glu::ProgramSeparable;
using glu::ProgramSources;
using glu::RenderContext;
using glu::ShaderProgram;
using glu::ShaderType;
using glu::Storage;
using glu::VariableDeclaration;
using glu::VarType;
using glu::VertexSource;
using std::map;
using std::ostringstream;
using std::set;
using std::string;
using std::vector;
using tcu::MessageBuilder;
using tcu::RenderTarget;
using tcu::ResultCollector;
using tcu::StringTemplate;
using tcu::Surface;
using tcu::TestLog;

using namespace glw;

#define LOG_CALL(CALL)        \
    do                        \
    {                         \
        enableLogging(true);  \
        CALL;                 \
        enableLogging(false); \
    } while (false)

enum
{
    VIEWPORT_SIZE = 128
};

enum VaryingInterpolation
{
    VARYINGINTERPOLATION_SMOOTH = 0,
    VARYINGINTERPOLATION_FLAT,
    VARYINGINTERPOLATION_CENTROID,
    VARYINGINTERPOLATION_DEFAULT,
    VARYINGINTERPOLATION_RANDOM,

    VARYINGINTERPOLATION_LAST
};

DataType randomType(Random &rnd)
{
    using namespace glu;

    if (rnd.getInt(0, 7) == 0)
    {
        const int numCols = rnd.getInt(2, 4), numRows = rnd.getInt(2, 4);

        return getDataTypeMatrix(numCols, numRows);
    }
    else
    {
        static const DataType s_types[] = {TYPE_FLOAT, TYPE_INT, TYPE_UINT};
        static const float s_weights[]  = {3.0, 1.0, 1.0};
        const int size                  = rnd.getInt(1, 4);
        const DataType scalarType =
            rnd.chooseWeighted<DataType>(DE_ARRAY_BEGIN(s_types), DE_ARRAY_END(s_types), DE_ARRAY_BEGIN(s_weights));
        return getDataTypeVector(scalarType, size);
    }

    DE_FATAL("Impossible");
    return TYPE_INVALID;
}

VaryingInterpolation randomInterpolation(Random &rnd)
{
    static const VaryingInterpolation s_validInterpolations[] = {
        VARYINGINTERPOLATION_SMOOTH,
        VARYINGINTERPOLATION_FLAT,
        VARYINGINTERPOLATION_CENTROID,
        VARYINGINTERPOLATION_DEFAULT,
    };
    return s_validInterpolations[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_validInterpolations) - 1)];
}

glu::Interpolation getGluInterpolation(VaryingInterpolation interpolation)
{
    switch (interpolation)
    {
    case VARYINGINTERPOLATION_SMOOTH:
        return glu::INTERPOLATION_SMOOTH;
    case VARYINGINTERPOLATION_FLAT:
        return glu::INTERPOLATION_FLAT;
    case VARYINGINTERPOLATION_CENTROID:
        return glu::INTERPOLATION_CENTROID;
    case VARYINGINTERPOLATION_DEFAULT:
        return glu::INTERPOLATION_LAST; //!< Last means no qualifier, i.e. default
    default:
        DE_FATAL("Invalid interpolation");
        return glu::INTERPOLATION_LAST;
    }
}

// used only for debug quick checks
#if defined(DE_DEBUG)
VaryingInterpolation getVaryingInterpolation(glu::Interpolation interpolation)
{
    switch (interpolation)
    {
    case glu::INTERPOLATION_SMOOTH:
        return VARYINGINTERPOLATION_SMOOTH;
    case glu::INTERPOLATION_FLAT:
        return VARYINGINTERPOLATION_FLAT;
    case glu::INTERPOLATION_CENTROID:
        return VARYINGINTERPOLATION_CENTROID;
    case glu::INTERPOLATION_LAST:
        return VARYINGINTERPOLATION_DEFAULT; //!< Last means no qualifier, i.e. default
    default:
        DE_FATAL("Invalid interpolation");
        return VARYINGINTERPOLATION_LAST;
    }
}
#endif

enum BindingKind
{
    BINDING_NAME,
    BINDING_LOCATION,
    BINDING_LAST
};

BindingKind randomBinding(Random &rnd)
{
    return rnd.getBool() ? BINDING_LOCATION : BINDING_NAME;
}

void printInputColor(ostringstream &oss, const VariableDeclaration &input)
{
    using namespace glu;

    const DataType basicType = input.varType.getBasicType();
    string exp               = input.name;

    switch (getDataTypeScalarType(basicType))
    {
    case TYPE_FLOAT:
        break;

    case TYPE_INT:
    case TYPE_UINT:
    {
        DataType floatType = getDataTypeFloatScalars(basicType);
        exp                = string() + "(" + getDataTypeName(floatType) + "(" + exp + ") / 255.0" + ")";
        break;
    }

    default:
        DE_FATAL("Impossible");
    }

    if (isDataTypeScalarOrVector(basicType))
    {
        switch (getDataTypeScalarSize(basicType))
        {
        case 1:
            oss << "hsv(vec3(" << exp << ", 1.0, 1.0))";
            break;
        case 2:
            oss << "hsv(vec3(" << exp << ", 1.0))";
            break;
        case 3:
            oss << "vec4(" << exp << ", 1.0)";
            break;
        case 4:
            oss << exp;
            break;
        default:
            DE_FATAL("Impossible");
        }
    }
    else if (isDataTypeMatrix(basicType))
    {
        int rows    = getDataTypeMatrixNumRows(basicType);
        int columns = getDataTypeMatrixNumColumns(basicType);

        if (rows == columns)
            oss << "hsv(vec3(determinant(" << exp << ")))";
        else
        {
            if (rows != 3 && columns >= 3)
            {
                exp = "transpose(" + exp + ")";
                std::swap(rows, columns);
            }
            exp = exp + "[0]";
            if (rows > 3)
                exp = exp + ".xyz";
            oss << "hsv(" << exp << ")";
        }
    }
    else
        DE_FATAL("Impossible");
}

// Representation for the varyings between vertex and fragment shaders

struct VaryingParams
{
    VaryingParams(void)
        : count(0)
        , type(glu::TYPE_LAST)
        , binding(BINDING_LAST)
        , vtxInterp(VARYINGINTERPOLATION_LAST)
        , frgInterp(VARYINGINTERPOLATION_LAST)
    {
    }

    int count;
    DataType type;
    BindingKind binding;
    VaryingInterpolation vtxInterp;
    VaryingInterpolation frgInterp;
};

struct VaryingInterface
{
    vector<VariableDeclaration> vtxOutputs;
    vector<VariableDeclaration> frgInputs;
};

// Generate corresponding input and output variable declarations that may vary
// in compatible ways.

VaryingInterpolation chooseInterpolation(VaryingInterpolation param, DataType type, Random &rnd)
{
    if (glu::getDataTypeScalarType(type) != glu::TYPE_FLOAT)
        return VARYINGINTERPOLATION_FLAT;

    if (param == VARYINGINTERPOLATION_RANDOM)
        return randomInterpolation(rnd);

    return param;
}

bool isSSOCompatibleInterpolation(VaryingInterpolation vertexInterpolation, VaryingInterpolation fragmentInterpolation)
{
    // interpolations must be fully specified
    DE_ASSERT(vertexInterpolation != VARYINGINTERPOLATION_RANDOM);
    DE_ASSERT(vertexInterpolation < VARYINGINTERPOLATION_LAST);
    DE_ASSERT(fragmentInterpolation != VARYINGINTERPOLATION_RANDOM);
    DE_ASSERT(fragmentInterpolation < VARYINGINTERPOLATION_LAST);

    // interpolation can only be either smooth or flat. Auxiliary storage does not matter.
    const bool isSmoothVtx =
        (vertexInterpolation == VARYINGINTERPOLATION_SMOOTH) ||  //!< trivial
        (vertexInterpolation == VARYINGINTERPOLATION_DEFAULT) || //!< default to smooth
        (vertexInterpolation == VARYINGINTERPOLATION_CENTROID);  //!< default to smooth, ignore storage
    const bool isSmoothFrag =
        (fragmentInterpolation == VARYINGINTERPOLATION_SMOOTH) ||  //!< trivial
        (fragmentInterpolation == VARYINGINTERPOLATION_DEFAULT) || //!< default to smooth
        (fragmentInterpolation == VARYINGINTERPOLATION_CENTROID);  //!< default to smooth, ignore storage
    // Khronos bug #12630: flat / smooth qualifiers must match in SSO
    return isSmoothVtx == isSmoothFrag;
}

VaryingInterface genVaryingInterface(const VaryingParams &params, Random &rnd)
{
    using namespace glu;

    VaryingInterface ret;
    int offset = 0;

    for (int varNdx = 0; varNdx < params.count; ++varNdx)
    {
        const BindingKind binding            = ((params.binding == BINDING_LAST) ? randomBinding(rnd) : params.binding);
        const DataType type                  = ((params.type == TYPE_LAST) ? randomType(rnd) : params.type);
        const VaryingInterpolation vtxInterp = chooseInterpolation(params.vtxInterp, type, rnd);
        const VaryingInterpolation frgInterp = chooseInterpolation(params.frgInterp, type, rnd);
        const VaryingInterpolation vtxCompatInterp =
            (isSSOCompatibleInterpolation(vtxInterp, frgInterp)) ? (vtxInterp) : (frgInterp);
        const int loc        = ((binding == BINDING_LOCATION) ? offset : -1);
        const string ndxStr  = de::toString(varNdx);
        const string vtxName = ((binding == BINDING_NAME) ? "var" + ndxStr : "vtxVar" + ndxStr);
        const string frgName = ((binding == BINDING_NAME) ? "var" + ndxStr : "frgVar" + ndxStr);
        const VarType varType(type, PRECISION_HIGHP);

        offset += getDataTypeNumLocations(type);

        // Over 16 locations aren't necessarily supported, so halt here.
        if (offset > 16)
            break;

        ret.vtxOutputs.push_back(
            VariableDeclaration(varType, vtxName, STORAGE_OUT, getGluInterpolation(vtxCompatInterp), loc));
        ret.frgInputs.push_back(VariableDeclaration(varType, frgName, STORAGE_IN, getGluInterpolation(frgInterp), loc));
    }

    return ret;
}

// Create vertex output variable declarations that are maximally compatible
// with the fragment input variables.

vector<VariableDeclaration> varyingCompatVtxOutputs(const VaryingInterface &varyings)
{
    vector<VariableDeclaration> outputs = varyings.vtxOutputs;

    for (size_t i = 0; i < outputs.size(); ++i)
    {
        outputs[i].interpolation = varyings.frgInputs[i].interpolation;
        outputs[i].name          = varyings.frgInputs[i].name;
    }

    return outputs;
}

// Shader source generation

void printFloat(ostringstream &oss, double d)
{
    oss.setf(oss.fixed | oss.internal);
    oss.precision(4);
    oss.width(7);
    oss << d;
}

void printFloatDeclaration(ostringstream &oss, const string &varName, bool uniform, GLfloat value = 0.0)
{
    using namespace glu;

    const VarType varType(TYPE_FLOAT, PRECISION_HIGHP);

    if (uniform)
        oss << VariableDeclaration(varType, varName, STORAGE_UNIFORM) << ";\n";
    else
        oss << VariableDeclaration(varType, varName, STORAGE_CONST) << " = " << de::floatToString(value, 6) << ";\n";
}

void printRandomInitializer(ostringstream &oss, DataType type, Random &rnd)
{
    using namespace glu;
    const int size = getDataTypeScalarSize(type);

    if (size > 0)
        oss << getDataTypeName(type) << "(";

    for (int i = 0; i < size; ++i)
    {
        oss << (i == 0 ? "" : ", ");
        switch (getDataTypeScalarType(type))
        {
        case TYPE_FLOAT:
            printFloat(oss, rnd.getInt(0, 16) / 16.0);
            break;

        case TYPE_INT:
        case TYPE_UINT:
            oss << rnd.getInt(0, 255);
            break;

        case TYPE_BOOL:
            oss << (rnd.getBool() ? "true" : "false");
            break;

        default:
            DE_FATAL("Impossible");
        }
    }

    if (size > 0)
        oss << ")";
}

string genVtxShaderSrc(uint32_t seed, const vector<VariableDeclaration> &outputs, const string &varName, bool uniform,
                       float value = 0.0)
{
    ostringstream oss;
    Random rnd(seed);
    enum
    {
        NUM_COMPONENTS = 2
    };
    static const int s_quadrants[][NUM_COMPONENTS] = {{1, 1}, {-1, 1}, {1, -1}};

    oss << "#version 310 es\n";

    printFloatDeclaration(oss, varName, uniform, value);

    for (vector<VariableDeclaration>::const_iterator it = outputs.begin(); it != outputs.end(); ++it)
        oss << *it << ";\n";

    oss << "const vec2 triangle[3] = vec2[3](\n";

    for (int vertexNdx = 0; vertexNdx < DE_LENGTH_OF_ARRAY(s_quadrants); ++vertexNdx)
    {
        oss << "\tvec2(";

        for (int componentNdx = 0; componentNdx < NUM_COMPONENTS; ++componentNdx)
        {
            printFloat(oss, s_quadrants[vertexNdx][componentNdx] * rnd.getInt(4, 16) / 16.0);
            oss << (componentNdx < 1 ? ", " : "");
        }

        oss << ")" << (vertexNdx < 2 ? "," : "") << "\n";
    }
    oss << ");\n";

    for (vector<VariableDeclaration>::const_iterator it = outputs.begin(); it != outputs.end(); ++it)
    {
        const DataType type   = it->varType.getBasicType();
        const string typeName = glu::getDataTypeName(type);

        oss << "const " << typeName << " " << it->name << "Inits[3] = " << typeName << "[3](\n";
        for (int i = 0; i < 3; ++i)
        {
            oss << (i == 0 ? "\t" : ",\n\t");
            printRandomInitializer(oss, type, rnd);
        }
        oss << ");\n";
    }

    oss << "void main (void)\n"
        << "{\n"
        << "\tgl_Position = vec4(" << varName << " * triangle[gl_VertexID], 0.0, 1.0);\n";

    for (vector<VariableDeclaration>::const_iterator it = outputs.begin(); it != outputs.end(); ++it)
        oss << "\t" << it->name << " = " << it->name << "Inits[gl_VertexID];\n";

    oss << "}\n";

    return oss.str();
}

string genFrgShaderSrc(uint32_t seed, const vector<VariableDeclaration> &inputs, const string &varName, bool uniform,
                       float value = 0.0)
{
    Random rnd(seed);
    ostringstream oss;

    oss.precision(4);
    oss.width(7);
    oss << "#version 310 es\n";

    oss << "precision highp float;\n";

    oss << "out vec4 fragColor;\n";

    printFloatDeclaration(oss, varName, uniform, value);

    for (vector<VariableDeclaration>::const_iterator it = inputs.begin(); it != inputs.end(); ++it)
        oss << *it << ";\n";

    // glsl % isn't defined for negative numbers
    oss << "int imod (int n, int d)"
        << "\n"
        << "{"
        << "\n"
        << "\t"
        << "return (n < 0 ? d - 1 - (-1 - n) % d : n % d);"
        << "\n"
        << "}"
        << "\n";

    oss << "vec4 hsv (vec3 hsv)"
        << "{"
        << "\n"
        << "\tfloat h = hsv.x * 3.0;\n"
        << "\tfloat r = max(0.0, 1.0 - h) + max(0.0, h - 2.0);\n"
        << "\tfloat g = max(0.0, 1.0 - abs(h - 1.0));\n"
        << "\tfloat b = max(0.0, 1.0 - abs(h - 2.0));\n"
        << "\tvec3 hs = mix(vec3(1.0), vec3(r, g, b), hsv.y);\n"
        << "\treturn vec4(hsv.z * hs, 1.0);\n"
        << "}\n";

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

    oss << "\t"
        << "fragColor = vec4(vec3(" << varName << "), 1.0);"
        << "\n";

    if (inputs.size() > 0)
    {
        oss << "\t"
            << "switch (imod(int(0.5 * (";

        printFloat(oss, rnd.getFloat(0.5f, 2.0f));
        oss << " * gl_FragCoord.x - ";

        printFloat(oss, rnd.getFloat(0.5f, 2.0f));
        oss << " * gl_FragCoord.y)), " << inputs.size() << "))"
            << "\n"
            << "\t"
            << "{"
            << "\n";

        for (size_t i = 0; i < inputs.size(); ++i)
        {
            oss << "\t\t"
                << "case " << i << ":"
                << "\n"
                << "\t\t\t"
                << "fragColor *= ";

            printInputColor(oss, inputs[i]);

            oss << ";"
                << "\n"
                << "\t\t\t"
                << "break;"
                << "\n";
        }

        oss << "\t\t"
            << "case " << inputs.size() << ":\n"
            << "\t\t\t"
            << "fragColor = vec4(1.0, 0.0, 1.0, 1.0);"
            << "\n";
        oss << "\t\t\t"
            << "break;"
            << "\n";

        oss << "\t\t"
            << "case -1:\n"
            << "\t\t\t"
            << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);"
            << "\n";
        oss << "\t\t\t"
            << "break;"
            << "\n";

        oss << "\t\t"
            << "default:"
            << "\n"
            << "\t\t\t"
            << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);"
            << "\n";

        oss << "\t"
            << "}\n";
    }

    oss << "}\n";

    return oss.str();
}

// ProgramWrapper

class ProgramWrapper
{
public:
    virtual ~ProgramWrapper(void)
    {
    }

    virtual GLuint getProgramName(void)   = 0;
    virtual void writeToLog(TestLog &log) = 0;
};

class ShaderProgramWrapper : public ProgramWrapper
{
public:
    ShaderProgramWrapper(const RenderContext &renderCtx, const ProgramSources &sources)
        : m_shaderProgram(renderCtx, sources)
    {
    }
    ~ShaderProgramWrapper(void)
    {
    }

    GLuint getProgramName(void)
    {
        return m_shaderProgram.getProgram();
    }
    ShaderProgram &getShaderProgram(void)
    {
        return m_shaderProgram;
    }
    void writeToLog(TestLog &log)
    {
        log << m_shaderProgram;
    }

private:
    ShaderProgram m_shaderProgram;
};

class RawProgramWrapper : public ProgramWrapper
{
public:
    RawProgramWrapper(const RenderContext &renderCtx, GLuint programName, ShaderType shaderType, const string &source)
        : m_program(renderCtx, programName)
        , m_shaderType(shaderType)
        , m_source(source)
    {
    }
    ~RawProgramWrapper(void)
    {
    }

    GLuint getProgramName(void)
    {
        return m_program.getProgram();
    }
    Program &getProgram(void)
    {
        return m_program;
    }
    void writeToLog(TestLog &log);

private:
    Program m_program;
    ShaderType m_shaderType;
    const string m_source;
};

void RawProgramWrapper::writeToLog(TestLog &log)
{
    const string info   = m_program.getInfoLog();
    qpShaderType qpType = glu::getLogShaderType(m_shaderType);

    log << TestLog::ShaderProgram(true, info)
        << TestLog::Shader(qpType, m_source, true, "[Shader created by glCreateShaderProgramv()]")
        << TestLog::EndShaderProgram;
}

// ProgramParams

struct ProgramParams
{
    ProgramParams(uint32_t vtxSeed_, GLfloat vtxScale_, uint32_t frgSeed_, GLfloat frgScale_)
        : vtxSeed(vtxSeed_)
        , vtxScale(vtxScale_)
        , frgSeed(frgSeed_)
        , frgScale(frgScale_)
    {
    }
    uint32_t vtxSeed;
    GLfloat vtxScale;
    uint32_t frgSeed;
    GLfloat frgScale;
};

ProgramParams genProgramParams(Random &rnd)
{
    const uint32_t vtxSeed = rnd.getUint32();
    const GLfloat vtxScale = (float)rnd.getInt(8, 16) / 16.0f;
    const uint32_t frgSeed = rnd.getUint32();
    const GLfloat frgScale = (float)rnd.getInt(0, 16) / 16.0f;

    return ProgramParams(vtxSeed, vtxScale, frgSeed, frgScale);
}

// TestParams

struct TestParams
{
    bool initSingle;
    bool switchVtx;
    bool switchFrg;
    bool useUniform;
    bool useSameName;
    bool useCreateHelper;
    bool useProgramUniform;
    VaryingParams varyings;
};

uint32_t paramsSeed(const TestParams &params)
{
    uint32_t paramCode =
        (params.initSingle << 0 | params.switchVtx << 1 | params.switchFrg << 2 | params.useUniform << 3 |
         params.useSameName << 4 | params.useCreateHelper << 5 | params.useProgramUniform << 6);

    paramCode = deUint32Hash(paramCode) + params.varyings.count;
    paramCode = deUint32Hash(paramCode) + params.varyings.type;
    paramCode = deUint32Hash(paramCode) + params.varyings.binding;
    paramCode = deUint32Hash(paramCode) + params.varyings.vtxInterp;
    paramCode = deUint32Hash(paramCode) + params.varyings.frgInterp;

    return deUint32Hash(paramCode);
}

string paramsCode(const TestParams &params)
{
    using namespace glu;

    ostringstream oss;

    oss << (params.initSingle ? "1" : "2") << (params.switchVtx ? "v" : "") << (params.switchFrg ? "f" : "")
        << (params.useProgramUniform ? "p" : "") << (params.useUniform ? "u" : "") << (params.useSameName ? "s" : "")
        << (params.useCreateHelper ? "c" : "") << de::toString(params.varyings.count)
        << (params.varyings.binding == BINDING_NAME     ? "n" :
            params.varyings.binding == BINDING_LOCATION ? "l" :
            params.varyings.binding == BINDING_LAST     ? "r" :
                                                          "")
        << (params.varyings.vtxInterp == VARYINGINTERPOLATION_SMOOTH   ? "m" :
            params.varyings.vtxInterp == VARYINGINTERPOLATION_CENTROID ? "e" :
            params.varyings.vtxInterp == VARYINGINTERPOLATION_FLAT     ? "a" :
            params.varyings.vtxInterp == VARYINGINTERPOLATION_RANDOM   ? "r" :
                                                                         "o")
        << (params.varyings.frgInterp == VARYINGINTERPOLATION_SMOOTH   ? "m" :
            params.varyings.frgInterp == VARYINGINTERPOLATION_CENTROID ? "e" :
            params.varyings.frgInterp == VARYINGINTERPOLATION_FLAT     ? "a" :
            params.varyings.frgInterp == VARYINGINTERPOLATION_RANDOM   ? "r" :
                                                                         "o");
    return oss.str();
}

bool paramsValid(const TestParams &params)
{
    using namespace glu;

    // Final pipeline has a single program?
    if (params.initSingle)
    {
        // Cannot have conflicting names for uniforms or constants
        if (params.useSameName)
            return false;

        // CreateShaderProgram would never get called
        if (!params.switchVtx && !params.switchFrg && params.useCreateHelper)
            return false;

        // Must switch either all or nothing
        if (params.switchVtx != params.switchFrg)
            return false;
    }

    // ProgramUniform would never get called
    if (params.useProgramUniform && !params.useUniform)
        return false;

    // Interpolation is meaningless if we don't use an in/out variable.
    if (params.varyings.count == 0 && !(params.varyings.vtxInterp == VARYINGINTERPOLATION_LAST &&
                                        params.varyings.frgInterp == VARYINGINTERPOLATION_LAST))
        return false;

    // Mismatch by flat / smooth is not allowed. See Khronos bug #12630
    // \note: iterpolations might be RANDOM, causing generated varyings potentially match / mismatch anyway.
    //        This is checked later on. Here, we just make sure that we don't force the generator to generate
    //        only invalid varying configurations, i.e. there exists a valid varying configuration for this
    //        test param config.
    if ((params.varyings.vtxInterp != VARYINGINTERPOLATION_RANDOM) &&
        (params.varyings.frgInterp != VARYINGINTERPOLATION_RANDOM) &&
        (params.varyings.vtxInterp == VARYINGINTERPOLATION_FLAT) !=
            (params.varyings.frgInterp == VARYINGINTERPOLATION_FLAT))
        return false;

    return true;
}

// used only for debug quick checks
#if defined(DE_DEBUG)
bool varyingsValid(const VaryingInterface &varyings)
{
    for (int ndx = 0; ndx < (int)varyings.vtxOutputs.size(); ++ndx)
    {
        const VaryingInterpolation vertexInterpolation =
            getVaryingInterpolation(varyings.vtxOutputs[ndx].interpolation);
        const VaryingInterpolation fragmentInterpolation =
            getVaryingInterpolation(varyings.frgInputs[ndx].interpolation);

        if (!isSSOCompatibleInterpolation(vertexInterpolation, fragmentInterpolation))
            return false;
    }

    return true;
}
#endif

void logParams(TestLog &log, const TestParams &params)
{
    // We don't log operational details here since those are shown
    // in the log messages during execution.
    MessageBuilder msg = log.message();

    msg << "Pipeline configuration:\n";

    msg << "Vertex and fragment shaders have " << (params.useUniform ? "uniform" : "constant") << "s with "
        << (params.useSameName ? "the same name" : "different names") << ".\n";

    if (params.varyings.count == 0)
        msg << "There are no varyings.\n";
    else
    {
        if (params.varyings.count == 1)
            msg << "There is one varying.\n";
        else
            msg << "There are " << params.varyings.count << " varyings.\n";

        if (params.varyings.type == glu::TYPE_LAST)
            msg << "Varyings are of random types.\n";
        else
            msg << "Varyings are of type '" << glu::getDataTypeName(params.varyings.type) << "'.\n";

        msg << "Varying outputs and inputs correspond ";
        switch (params.varyings.binding)
        {
        case BINDING_NAME:
            msg << "by name.\n";
            break;
        case BINDING_LOCATION:
            msg << "by location.\n";
            break;
        case BINDING_LAST:
            msg << "randomly either by name or by location.\n";
            break;
        default:
            DE_FATAL("Impossible");
        }

        msg << "In the vertex shader the varyings are qualified ";
        if (params.varyings.vtxInterp == VARYINGINTERPOLATION_DEFAULT)
            msg << "with no interpolation qualifiers.\n";
        else if (params.varyings.vtxInterp == VARYINGINTERPOLATION_RANDOM)
            msg << "with a random interpolation qualifier.\n";
        else
            msg << "'" << glu::getInterpolationName(getGluInterpolation(params.varyings.vtxInterp)) << "'.\n";

        msg << "In the fragment shader the varyings are qualified ";
        if (params.varyings.frgInterp == VARYINGINTERPOLATION_DEFAULT)
            msg << "with no interpolation qualifiers.\n";
        else if (params.varyings.frgInterp == VARYINGINTERPOLATION_RANDOM)
            msg << "with a random interpolation qualifier.\n";
        else
            msg << "'" << glu::getInterpolationName(getGluInterpolation(params.varyings.frgInterp)) << "'.\n";
    }

    msg << TestLog::EndMessage;

    log.writeMessage("");
}

TestParams genParams(uint32_t seed)
{
    Random rnd(seed);
    TestParams params;
    int tryNdx = 0;

    do
    {
        params.initSingle        = rnd.getBool();
        params.switchVtx         = rnd.getBool();
        params.switchFrg         = rnd.getBool();
        params.useUniform        = rnd.getBool();
        params.useProgramUniform = params.useUniform && rnd.getBool();
        params.useCreateHelper   = rnd.getBool();
        params.useSameName       = rnd.getBool();
        {
            int i                 = rnd.getInt(-1, 3);
            params.varyings.count = (i == -1 ? 0 : 1 << i);
        }
        if (params.varyings.count > 0)
        {
            params.varyings.type      = glu::TYPE_LAST;
            params.varyings.binding   = BINDING_LAST;
            params.varyings.vtxInterp = VARYINGINTERPOLATION_RANDOM;
            params.varyings.frgInterp = VARYINGINTERPOLATION_RANDOM;
        }
        else
        {
            params.varyings.type      = glu::TYPE_INVALID;
            params.varyings.binding   = BINDING_LAST;
            params.varyings.vtxInterp = VARYINGINTERPOLATION_LAST;
            params.varyings.frgInterp = VARYINGINTERPOLATION_LAST;
        }

        tryNdx += 1;
    } while (!paramsValid(params) && tryNdx < 16);

    DE_ASSERT(paramsValid(params));

    return params;
}

// Program pipeline wrapper that retains references to component programs.

struct Pipeline
{
    Pipeline(MovePtr<ProgramPipeline> pipeline_, MovePtr<ProgramWrapper> fullProg_, MovePtr<ProgramWrapper> vtxProg_,
             MovePtr<ProgramWrapper> frgProg_)
        : pipeline(pipeline_)
        , fullProg(fullProg_)
        , vtxProg(vtxProg_)
        , frgProg(frgProg_)
    {
    }

    ProgramWrapper &getVertexProgram(void) const
    {
        return vtxProg ? *vtxProg : *fullProg;
    }

    ProgramWrapper &getFragmentProgram(void) const
    {
        return frgProg ? *frgProg : *fullProg;
    }

    UniquePtr<ProgramPipeline> pipeline;
    UniquePtr<ProgramWrapper> fullProg;
    UniquePtr<ProgramWrapper> vtxProg;
    UniquePtr<ProgramWrapper> frgProg;
};

void logPipeline(TestLog &log, const Pipeline &pipeline)
{
    ProgramWrapper &vtxProg = pipeline.getVertexProgram();
    ProgramWrapper &frgProg = pipeline.getFragmentProgram();

    log.writeMessage("// Failed program pipeline:");
    if (&vtxProg == &frgProg)
    {
        log.writeMessage("// Common program for both vertex and fragment stages:");
        vtxProg.writeToLog(log);
    }
    else
    {
        log.writeMessage("// Vertex stage program:");
        vtxProg.writeToLog(log);
        log.writeMessage("// Fragment stage program:");
        frgProg.writeToLog(log);
    }
}

// Rectangle

struct Rectangle
{
    Rectangle(int x_, int y_, int width_, int height_) : x(x_), y(y_), width(width_), height(height_)
    {
    }
    int x;
    int y;
    int width;
    int height;
};

void setViewport(const RenderContext &renderCtx, const Rectangle &rect)
{
    renderCtx.getFunctions().viewport(rect.x, rect.y, rect.width, rect.height);
}

void readRectangle(const RenderContext &renderCtx, const Rectangle &rect, Surface &dst)
{
    dst.setSize(rect.width, rect.height);
    glu::readPixels(renderCtx, rect.x, rect.y, dst.getAccess());
}

Rectangle randomViewport(const RenderContext &ctx, Random &rnd, GLint maxWidth, GLint maxHeight)
{
    const RenderTarget &target = ctx.getRenderTarget();
    GLint width                = de::min(target.getWidth(), maxWidth);
    GLint xOff                 = rnd.getInt(0, target.getWidth() - width);
    GLint height               = de::min(target.getHeight(), maxHeight);
    GLint yOff                 = rnd.getInt(0, target.getHeight() - height);

    return Rectangle(xOff, yOff, width, height);
}

// SeparateShaderTest

class SeparateShaderTest : public TestCase, private CallLogWrapper
{
public:
    typedef void (SeparateShaderTest::*TestFunc)(MovePtr<Pipeline> &pipeOut);

    SeparateShaderTest(Context &ctx, const string &name, const string &description, int iterations,
                       const TestParams &params, TestFunc testFunc);

    IterateResult iterate(void);

    void testPipelineRendering(MovePtr<Pipeline> &pipeOut);
    void testCurrentProgPriority(MovePtr<Pipeline> &pipeOut);
    void testActiveProgramUniform(MovePtr<Pipeline> &pipeOut);
    void testPipelineQueryActive(MovePtr<Pipeline> &pipeOut);
    void testPipelineQueryPrograms(MovePtr<Pipeline> &pipeOut);

private:
    TestLog &log(void);
    const RenderContext &getRenderContext(void);

    void setUniform(ProgramWrapper &program, const string &uniformName, GLfloat value, bool useProgramUni);

    void drawSurface(Surface &dst, uint32_t seed = 0);

    MovePtr<ProgramWrapper> createShaderProgram(const string *vtxSource, const string *frgSource, bool separable);

    MovePtr<ProgramWrapper> createSingleShaderProgram(ShaderType shaderType, const string &src);

    MovePtr<Pipeline> createPipeline(const ProgramParams &pp);

    MovePtr<ProgramWrapper> createReferenceProgram(const ProgramParams &pp);

    int m_iterations;
    int m_currentIteration;
    TestParams m_params;
    TestFunc m_testFunc;
    Random m_rnd;
    ResultCollector m_status;
    VaryingInterface m_varyings;

    // Per-iteration state required for logging on exception
    MovePtr<ProgramWrapper> m_fullProg;
    MovePtr<ProgramWrapper> m_vtxProg;
    MovePtr<ProgramWrapper> m_frgProg;
};

const RenderContext &SeparateShaderTest::getRenderContext(void)
{
    return m_context.getRenderContext();
}

TestLog &SeparateShaderTest::log(void)
{
    return m_testCtx.getLog();
}

SeparateShaderTest::SeparateShaderTest(Context &ctx, const string &name, const string &description, int iterations,
                                       const TestParams &params, TestFunc testFunc)
    : TestCase(ctx, name.c_str(), description.c_str())
    , CallLogWrapper(ctx.getRenderContext().getFunctions(), log())
    , m_iterations(iterations)
    , m_currentIteration(0)
    , m_params(params)
    , m_testFunc(testFunc)
    , m_rnd(paramsSeed(params))
    , m_status(log(), "// ")
    , m_varyings(genVaryingInterface(params.varyings, m_rnd))
{
    DE_ASSERT(paramsValid(params));
    DE_ASSERT(varyingsValid(m_varyings));
}

MovePtr<ProgramWrapper> SeparateShaderTest::createShaderProgram(const string *vtxSource, const string *frgSource,
                                                                bool separable)
{
    ProgramSources sources;

    if (vtxSource != DE_NULL)
        sources << VertexSource(*vtxSource);
    if (frgSource != DE_NULL)
        sources << FragmentSource(*frgSource);
    sources << ProgramSeparable(separable);

    MovePtr<ShaderProgramWrapper> wrapper(new ShaderProgramWrapper(getRenderContext(), sources));
    if (!wrapper->getShaderProgram().isOk())
    {
        log().writeMessage("Couldn't create shader program");
        wrapper->writeToLog(log());
        TCU_FAIL("Couldn't create shader program");
    }

    return MovePtr<ProgramWrapper>(wrapper.release());
}

MovePtr<ProgramWrapper> SeparateShaderTest::createSingleShaderProgram(ShaderType shaderType, const string &src)
{
    const RenderContext &renderCtx = getRenderContext();

    if (m_params.useCreateHelper)
    {
        const char *const srcStr = src.c_str();
        const GLenum glType      = glu::getGLShaderType(shaderType);
        const GLuint programName = glCreateShaderProgramv(glType, 1, &srcStr);

        if (glGetError() != GL_NO_ERROR || programName == 0)
        {
            qpShaderType qpType = glu::getLogShaderType(shaderType);

            log() << TestLog::Message << "glCreateShaderProgramv() failed" << TestLog::EndMessage
                  << TestLog::ShaderProgram(false, "[glCreateShaderProgramv() failed]")
                  << TestLog::Shader(qpType, src, false, "[glCreateShaderProgramv() failed]")
                  << TestLog::EndShaderProgram;
            TCU_FAIL("glCreateShaderProgramv() failed");
        }

        RawProgramWrapper *const wrapper = new RawProgramWrapper(renderCtx, programName, shaderType, src);
        MovePtr<ProgramWrapper> wrapperPtr(wrapper);
        Program &program = wrapper->getProgram();

        if (!program.getLinkStatus())
        {
            log().writeMessage("glCreateShaderProgramv() failed at linking");
            wrapper->writeToLog(log());
            TCU_FAIL("glCreateShaderProgram() failed at linking");
        }
        return wrapperPtr;
    }
    else
    {
        switch (shaderType)
        {
        case glu::SHADERTYPE_VERTEX:
            return createShaderProgram(&src, DE_NULL, true);
        case glu::SHADERTYPE_FRAGMENT:
            return createShaderProgram(DE_NULL, &src, true);
        default:
            DE_FATAL("Impossible case");
        }
    }
    return MovePtr<ProgramWrapper>(); // Shut up compiler warnings.
}

void SeparateShaderTest::setUniform(ProgramWrapper &program, const string &uniformName, GLfloat value,
                                    bool useProgramUniform)
{
    const GLuint progName = program.getProgramName();
    const GLint location  = glGetUniformLocation(progName, uniformName.c_str());
    MessageBuilder msg    = log().message();

    msg << "// Set program " << progName << "'s uniform '" << uniformName << "' to " << value;
    if (useProgramUniform)
    {
        msg << " using glProgramUniform1f";
        glProgramUniform1f(progName, location, value);
    }
    else
    {
        msg << " using glUseProgram and glUniform1f";
        glUseProgram(progName);
        glUniform1f(location, value);
        glUseProgram(0);
    }
    msg << TestLog::EndMessage;
}

MovePtr<Pipeline> SeparateShaderTest::createPipeline(const ProgramParams &pp)
{
    const bool useUniform      = m_params.useUniform;
    const string vtxName       = m_params.useSameName ? "scale" : "vtxScale";
    const uint32_t initVtxSeed = m_params.switchVtx ? m_rnd.getUint32() : pp.vtxSeed;

    const string frgName       = m_params.useSameName ? "scale" : "frgScale";
    const uint32_t initFrgSeed = m_params.switchFrg ? m_rnd.getUint32() : pp.frgSeed;
    const string frgSource     = genFrgShaderSrc(initFrgSeed, m_varyings.frgInputs, frgName, useUniform, pp.frgScale);

    const RenderContext &renderCtx = getRenderContext();
    MovePtr<ProgramPipeline> pipeline(new ProgramPipeline(renderCtx));
    MovePtr<ProgramWrapper> fullProg;
    MovePtr<ProgramWrapper> vtxProg;
    MovePtr<ProgramWrapper> frgProg;

    // We cannot allow a situation where we have a single program with a
    // single uniform, because then the vertex and fragment shader uniforms
    // would not be distinct in the final pipeline, and we are going to test
    // that altering one uniform will not affect the other.
    DE_ASSERT(!(m_params.initSingle && m_params.useSameName && !m_params.switchVtx && !m_params.switchFrg));

    if (m_params.initSingle)
    {
        string vtxSource =
            genVtxShaderSrc(initVtxSeed, varyingCompatVtxOutputs(m_varyings), vtxName, useUniform, pp.vtxScale);
        fullProg = createShaderProgram(&vtxSource, &frgSource, true);
        pipeline->useProgramStages(GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, fullProg->getProgramName());
        log() << TestLog::Message << "// Created pipeline " << pipeline->getPipeline() << " with two-shader program "
              << fullProg->getProgramName() << TestLog::EndMessage;
    }
    else
    {
        string vtxSource = genVtxShaderSrc(initVtxSeed, m_varyings.vtxOutputs, vtxName, useUniform, pp.vtxScale);
        vtxProg          = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, vtxSource);
        pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());

        frgProg = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, frgSource);
        pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());

        log() << TestLog::Message << "// Created pipeline " << pipeline->getPipeline() << " with vertex program "
              << vtxProg->getProgramName() << " and fragment program " << frgProg->getProgramName()
              << TestLog::EndMessage;
    }

    m_status.check(pipeline->isValid(), "Pipeline is invalid after initialization");

    if (m_params.switchVtx)
    {
        string newSource = genVtxShaderSrc(pp.vtxSeed, m_varyings.vtxOutputs, vtxName, useUniform, pp.vtxScale);
        vtxProg          = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, newSource);
        pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());
        log() << TestLog::Message << "// Switched pipeline " << pipeline->getPipeline()
              << "'s vertex stage to single-shader program " << vtxProg->getProgramName() << TestLog::EndMessage;
    }
    if (m_params.switchFrg)
    {
        string newSource = genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs, frgName, useUniform, pp.frgScale);
        frgProg          = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, newSource);
        pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());
        log() << TestLog::Message << "// Switched pipeline " << pipeline->getPipeline()
              << "'s fragment stage to single-shader program " << frgProg->getProgramName() << TestLog::EndMessage;
    }

    if (m_params.switchVtx || m_params.switchFrg)
        m_status.check(pipeline->isValid(), "Pipeline became invalid after changing a stage's program");

    if (m_params.useUniform)
    {
        ProgramWrapper &vtxStage = *(vtxProg ? vtxProg : fullProg);
        ProgramWrapper &frgStage = *(frgProg ? frgProg : fullProg);

        setUniform(vtxStage, vtxName, pp.vtxScale, m_params.useProgramUniform);
        setUniform(frgStage, frgName, pp.frgScale, m_params.useProgramUniform);
    }
    else
        log().writeMessage("// Programs use constants instead of uniforms");

    return MovePtr<Pipeline>(new Pipeline(pipeline, fullProg, vtxProg, frgProg));
}

MovePtr<ProgramWrapper> SeparateShaderTest::createReferenceProgram(const ProgramParams &pp)
{
    bool useUniform = m_params.useUniform;
    const string vtxSrc =
        genVtxShaderSrc(pp.vtxSeed, varyingCompatVtxOutputs(m_varyings), "vtxScale", useUniform, pp.vtxScale);
    const string frgSrc = genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs, "frgScale", useUniform, pp.frgScale);
    MovePtr<ProgramWrapper> program = createShaderProgram(&vtxSrc, &frgSrc, false);
    GLuint progName                 = program->getProgramName();

    log() << TestLog::Message << "// Created monolithic shader program " << progName << TestLog::EndMessage;

    if (useUniform)
    {
        setUniform(*program, "vtxScale", pp.vtxScale, false);
        setUniform(*program, "frgScale", pp.frgScale, false);
    }

    return program;
}

void SeparateShaderTest::drawSurface(Surface &dst, uint32_t seed)
{
    const RenderContext &renderCtx = getRenderContext();
    Random rnd(seed > 0 ? seed : m_rnd.getUint32());
    Rectangle viewport = randomViewport(renderCtx, rnd, VIEWPORT_SIZE, VIEWPORT_SIZE);
    uint32_t vao       = 0;

    if (!glu::isContextTypeES(renderCtx.getType()))
    {
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
    }

    glClearColor(0.125f, 0.25f, 0.5f, 1.f);
    setViewport(renderCtx, viewport);
    glClear(GL_COLOR_BUFFER_BIT);
    GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
    readRectangle(renderCtx, viewport, dst);
    log().writeMessage("// Drew a triangle");

    if (vao)
        glDeleteVertexArrays(1, &vao);
}

void SeparateShaderTest::testPipelineRendering(MovePtr<Pipeline> &pipeOut)
{
    ProgramParams pp   = genProgramParams(m_rnd);
    Pipeline &pipeline = *(pipeOut = createPipeline(pp));
    GLuint pipeName    = pipeline.pipeline->getPipeline();
    UniquePtr<ProgramWrapper> refProgram(createReferenceProgram(pp));
    GLuint refProgName = refProgram->getProgramName();
    Surface refSurface;
    Surface pipelineSurface;
    GLuint drawSeed = m_rnd.getUint32();

    glUseProgram(refProgName);
    log() << TestLog::Message << "// Use program " << refProgName << TestLog::EndMessage;
    drawSurface(refSurface, drawSeed);
    glUseProgram(0);

    glBindProgramPipeline(pipeName);
    log() << TestLog::Message << "// Bind pipeline " << pipeName << TestLog::EndMessage;
    drawSurface(pipelineSurface, drawSeed);
    glBindProgramPipeline(0);

    {
        const bool result = tcu::fuzzyCompare(m_testCtx.getLog(), "Program pipeline result",
                                              "Result of comparing a program pipeline with a monolithic program",
                                              refSurface, pipelineSurface, 0.05f, tcu::COMPARE_LOG_RESULT);

        m_status.check(result, "Pipeline rendering differs from equivalent monolithic program");
    }
}

void SeparateShaderTest::testCurrentProgPriority(MovePtr<Pipeline> &pipeOut)
{
    ProgramParams pipePp    = genProgramParams(m_rnd);
    ProgramParams programPp = genProgramParams(m_rnd);
    Pipeline &pipeline      = *(pipeOut = createPipeline(pipePp));
    GLuint pipeName         = pipeline.pipeline->getPipeline();
    UniquePtr<ProgramWrapper> program(createReferenceProgram(programPp));
    Surface pipelineSurface;
    Surface refSurface;
    Surface resultSurface;
    uint32_t drawSeed = m_rnd.getUint32();

    LOG_CALL(glBindProgramPipeline(pipeName));
    drawSurface(pipelineSurface, drawSeed);
    LOG_CALL(glBindProgramPipeline(0));

    LOG_CALL(glUseProgram(program->getProgramName()));
    drawSurface(refSurface, drawSeed);
    LOG_CALL(glUseProgram(0));

    LOG_CALL(glUseProgram(program->getProgramName()));
    LOG_CALL(glBindProgramPipeline(pipeName));
    drawSurface(resultSurface, drawSeed);
    LOG_CALL(glBindProgramPipeline(0));
    LOG_CALL(glUseProgram(0));

    bool result = tcu::pixelThresholdCompare(m_testCtx.getLog(), "Active program rendering result",
                                             "Active program rendering result", refSurface, resultSurface,
                                             tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);

    m_status.check(result, "glBindProgramPipeline() affects glUseProgram()");
    if (!result)
        log() << TestLog::Image("Pipeline image", "Image produced by pipeline", pipelineSurface);
}

void SeparateShaderTest::testActiveProgramUniform(MovePtr<Pipeline> &pipeOut)
{
    ProgramParams refPp = genProgramParams(m_rnd);
    Surface refSurface;
    Surface resultSurface;
    uint32_t drawSeed = m_rnd.getUint32();

    DE_UNREF(pipeOut);
    {
        UniquePtr<ProgramWrapper> refProg(createReferenceProgram(refPp));
        GLuint refProgName = refProg->getProgramName();

        glUseProgram(refProgName);
        log() << TestLog::Message << "// Use reference program " << refProgName << TestLog::EndMessage;
        drawSurface(refSurface, drawSeed);
        glUseProgram(0);
    }

    {
        ProgramParams changePp = genProgramParams(m_rnd);
        changePp.vtxSeed       = refPp.vtxSeed;
        changePp.frgSeed       = refPp.frgSeed;
        UniquePtr<ProgramWrapper> changeProg(createReferenceProgram(changePp));
        GLuint changeName = changeProg->getProgramName();
        ProgramPipeline pipeline(getRenderContext());
        GLint vtxLoc = glGetUniformLocation(changeName, "vtxScale");
        GLint frgLoc = glGetUniformLocation(changeName, "frgScale");

        LOG_CALL(glBindProgramPipeline(pipeline.getPipeline()));

        pipeline.activeShaderProgram(changeName);
        log() << TestLog::Message << "// Set active shader program to " << changeName << TestLog::EndMessage;

        glUniform1f(vtxLoc, refPp.vtxScale);
        log() << TestLog::Message << "// Set uniform 'vtxScale' to " << refPp.vtxScale << " using glUniform1f"
              << TestLog::EndMessage;
        glUniform1f(frgLoc, refPp.frgScale);
        log() << TestLog::Message << "// Set uniform 'frgScale' to " << refPp.frgScale << " using glUniform1f"
              << TestLog::EndMessage;

        pipeline.activeShaderProgram(0);
        LOG_CALL(glBindProgramPipeline(0));

        LOG_CALL(glUseProgram(changeName));
        drawSurface(resultSurface, drawSeed);
        LOG_CALL(glUseProgram(0));
    }

    bool result =
        tcu::fuzzyCompare(m_testCtx.getLog(), "Active program uniform result", "Active program uniform result",
                          refSurface, resultSurface, 0.05f, tcu::COMPARE_LOG_RESULT);

    m_status.check(result, "glUniform() did not correctly modify "
                           "the active program of the bound pipeline");
}

void SeparateShaderTest::testPipelineQueryPrograms(MovePtr<Pipeline> &pipeOut)
{
    ProgramParams pipePp = genProgramParams(m_rnd);
    Pipeline &pipeline   = *(pipeOut = createPipeline(pipePp));
    GLuint pipeName      = pipeline.pipeline->getPipeline();
    GLint queryVtx       = 0;
    GLint queryFrg       = 0;

    LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_VERTEX_SHADER, &queryVtx)));
    m_status.check(GLuint(queryVtx) == pipeline.getVertexProgram().getProgramName(),
                   "Program pipeline query reported wrong vertex shader program");

    LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_FRAGMENT_SHADER, &queryFrg)));
    m_status.check(GLuint(queryFrg) == pipeline.getFragmentProgram().getProgramName(),
                   "Program pipeline query reported wrong fragment shader program");
}

void SeparateShaderTest::testPipelineQueryActive(MovePtr<Pipeline> &pipeOut)
{
    ProgramParams pipePp = genProgramParams(m_rnd);
    Pipeline &pipeline   = *(pipeOut = createPipeline(pipePp));
    GLuint pipeName      = pipeline.pipeline->getPipeline();
    GLuint newActive     = pipeline.getVertexProgram().getProgramName();
    GLint queryActive    = 0;

    LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
    m_status.check(queryActive == 0, "Program pipeline query reported non-zero initial active program");

    pipeline.pipeline->activeShaderProgram(newActive);
    log() << TestLog::Message << "Set pipeline " << pipeName << "'s active shader program to " << newActive
          << TestLog::EndMessage;

    LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
    m_status.check(GLuint(queryActive) == newActive, "Program pipeline query reported incorrect active program");

    pipeline.pipeline->activeShaderProgram(0);
}

TestCase::IterateResult SeparateShaderTest::iterate(void)
{
    MovePtr<Pipeline> pipeline;

    DE_ASSERT(m_iterations > 0);

    if (m_currentIteration == 0)
        logParams(log(), m_params);

    ++m_currentIteration;

    try
    {
        (this->*m_testFunc)(pipeline);
        log().writeMessage("");
    }
    catch (const tcu::Exception &)
    {
        if (pipeline)
            logPipeline(log(), *pipeline);
        throw;
    }

    if (m_status.getResult() != QP_TEST_RESULT_PASS)
    {
        if (pipeline)
            logPipeline(log(), *pipeline);
    }
    else if (m_currentIteration < m_iterations)
        return CONTINUE;

    m_status.setTestContextResult(m_testCtx);
    return STOP;
}

// Group construction utilities

enum ParamFlags
{
    PARAMFLAGS_SWITCH_FRAGMENT = 1 << 0,
    PARAMFLAGS_SWITCH_VERTEX   = 1 << 1,
    PARAMFLAGS_INIT_SINGLE     = 1 << 2,
    PARAMFLAGS_LAST            = 1 << 3,
    PARAMFLAGS_MASK            = PARAMFLAGS_LAST - 1
};

bool areCaseParamFlagsValid(ParamFlags flags)
{
    const ParamFlags switchAll = ParamFlags(PARAMFLAGS_SWITCH_VERTEX | PARAMFLAGS_SWITCH_FRAGMENT);

    if ((flags & PARAMFLAGS_INIT_SINGLE) != 0)
        return (flags & switchAll) == 0 || (flags & switchAll) == switchAll;
    else
        return true;
}

bool addRenderTest(TestCaseGroup &group, const string &namePrefix, const string &descPrefix, int numIterations,
                   ParamFlags flags, TestParams params)
{
    ostringstream name;
    ostringstream desc;

    DE_ASSERT(areCaseParamFlagsValid(flags));

    name << namePrefix;
    desc << descPrefix;

    params.initSingle = (flags & PARAMFLAGS_INIT_SINGLE) != 0;
    params.switchVtx  = (flags & PARAMFLAGS_SWITCH_VERTEX) != 0;
    params.switchFrg  = (flags & PARAMFLAGS_SWITCH_FRAGMENT) != 0;

    name << (flags & PARAMFLAGS_INIT_SINGLE ? "single_program" : "separate_programs");
    desc << (flags & PARAMFLAGS_INIT_SINGLE ? "Single program with two shaders" : "Separate programs for each shader");

    switch (flags & (PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX))
    {
    case 0:
        break;
    case PARAMFLAGS_SWITCH_FRAGMENT:
        name << "_add_fragment";
        desc << ", then add a fragment program";
        break;
    case PARAMFLAGS_SWITCH_VERTEX:
        name << "_add_vertex";
        desc << ", then add a vertex program";
        break;
    case PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX:
        name << "_add_both";
        desc << ", then add both vertex and shader programs";
        break;
    }

    if (!paramsValid(params))
        return false;

    group.addChild(new SeparateShaderTest(group.getContext(), name.str(), desc.str(), numIterations, params,
                                          &SeparateShaderTest::testPipelineRendering));

    return true;
}

void describeInterpolation(const string &stage, VaryingInterpolation qual, ostringstream &name, ostringstream &desc)
{
    DE_ASSERT(qual < VARYINGINTERPOLATION_RANDOM);

    if (qual == VARYINGINTERPOLATION_DEFAULT)
    {
        desc << ", unqualified in " << stage << " shader";
        return;
    }
    else
    {
        const string qualName = glu::getInterpolationName(getGluInterpolation(qual));

        name << "_" << stage << "_" << qualName;
        desc << ", qualified '" << qualName << "' in " << stage << " shader";
    }
}

} // namespace

TestCaseGroup *createCommonSeparateShaderTests(Context &ctx)
{
    TestParams defaultParams;
    int numIterations    = 4;
    TestCaseGroup *group = new TestCaseGroup(ctx, "separate_shader", "Separate shader tests");

    defaultParams.useUniform         = false;
    defaultParams.initSingle         = false;
    defaultParams.switchVtx          = false;
    defaultParams.switchFrg          = false;
    defaultParams.useCreateHelper    = false;
    defaultParams.useProgramUniform  = false;
    defaultParams.useSameName        = false;
    defaultParams.varyings.count     = 0;
    defaultParams.varyings.type      = glu::TYPE_INVALID;
    defaultParams.varyings.binding   = BINDING_NAME;
    defaultParams.varyings.vtxInterp = VARYINGINTERPOLATION_LAST;
    defaultParams.varyings.frgInterp = VARYINGINTERPOLATION_LAST;

    TestCaseGroup *stagesGroup = new TestCaseGroup(ctx, "pipeline", "Pipeline configuration tests");
    group->addChild(stagesGroup);

    for (uint32_t flags = 0; flags < PARAMFLAGS_LAST << 2; ++flags)
    {
        TestParams params = defaultParams;
        ostringstream name;
        ostringstream desc;

        if (!areCaseParamFlagsValid(ParamFlags(flags & PARAMFLAGS_MASK)))
            continue;

        if (flags & (PARAMFLAGS_LAST << 1))
        {
            params.useSameName = true;
            name << "same_";
            desc << "Identically named ";
        }
        else
        {
            name << "different_";
            desc << "Differently named ";
        }

        if (flags & PARAMFLAGS_LAST)
        {
            params.useUniform = true;
            name << "uniform_";
            desc << "uniforms, ";
        }
        else
        {
            name << "constant_";
            desc << "constants, ";
        }

        addRenderTest(*stagesGroup, name.str(), desc.str(), numIterations, ParamFlags(flags & PARAMFLAGS_MASK), params);
    }

    TestCaseGroup *programUniformGroup = new TestCaseGroup(ctx, "program_uniform", "ProgramUniform tests");
    group->addChild(programUniformGroup);

    for (uint32_t flags = 0; flags < PARAMFLAGS_LAST; ++flags)
    {
        TestParams params = defaultParams;

        if (!areCaseParamFlagsValid(ParamFlags(flags)))
            continue;

        params.useUniform        = true;
        params.useProgramUniform = true;

        addRenderTest(*programUniformGroup, "", "", numIterations, ParamFlags(flags), params);
    }

    TestCaseGroup *createShaderProgramGroup =
        new TestCaseGroup(ctx, "create_shader_program", "CreateShaderProgram tests");
    group->addChild(createShaderProgramGroup);

    for (uint32_t flags = 0; flags < PARAMFLAGS_LAST; ++flags)
    {
        TestParams params = defaultParams;

        if (!areCaseParamFlagsValid(ParamFlags(flags)))
            continue;

        params.useCreateHelper = true;

        addRenderTest(*createShaderProgramGroup, "", "", numIterations, ParamFlags(flags), params);
    }

    TestCaseGroup *interfaceGroup = new TestCaseGroup(ctx, "interface", "Shader interface compatibility tests");
    group->addChild(interfaceGroup);

    enum
    {
        NUM_INTERPOLATIONS =
            VARYINGINTERPOLATION_RANDOM, // VARYINGINTERPOLATION_RANDOM is one after last fully specified interpolation
        INTERFACEFLAGS_LAST =
            static_cast<int>(BINDING_LAST) * static_cast<int>(NUM_INTERPOLATIONS) * static_cast<int>(NUM_INTERPOLATIONS)
    };

    for (uint32_t flags = 0; flags < INTERFACEFLAGS_LAST; ++flags)
    {
        uint32_t tmpFlags              = flags;
        VaryingInterpolation frgInterp = VaryingInterpolation(tmpFlags % NUM_INTERPOLATIONS);
        VaryingInterpolation vtxInterp = VaryingInterpolation((tmpFlags /= NUM_INTERPOLATIONS) % NUM_INTERPOLATIONS);
        BindingKind binding            = BindingKind((tmpFlags /= NUM_INTERPOLATIONS) % BINDING_LAST);
        TestParams params              = defaultParams;
        ostringstream name;
        ostringstream desc;

        params.varyings.count     = 1;
        params.varyings.type      = glu::TYPE_FLOAT;
        params.varyings.binding   = binding;
        params.varyings.vtxInterp = vtxInterp;
        params.varyings.frgInterp = frgInterp;

        switch (binding)
        {
        case BINDING_LOCATION:
            name << "same_location";
            desc << "Varyings have same location, ";
            break;
        case BINDING_NAME:
            name << "same_name";
            desc << "Varyings have same name, ";
            break;
        default:
            DE_FATAL("Impossible");
        }

        describeInterpolation("vertex", vtxInterp, name, desc);
        describeInterpolation("fragment", frgInterp, name, desc);

        if (!paramsValid(params))
            continue;

        interfaceGroup->addChild(new SeparateShaderTest(ctx, name.str(), desc.str(), numIterations, params,
                                                        &SeparateShaderTest::testPipelineRendering));
    }

    uint32_t baseSeed = ctx.getTestContext().getCommandLine().getBaseSeed();
    Random rnd(deStringHash("separate_shader.random") + baseSeed);
    set<string> seen;
    TestCaseGroup *randomGroup = new TestCaseGroup(ctx, "random", "Random pipeline configuration tests");
    group->addChild(randomGroup);

    for (uint32_t i = 0; i < 128; ++i)
    {
        TestParams params;
        string code;
        uint32_t genIterations = 4096;

        do
        {
            params = genParams(rnd.getUint32());
            code   = paramsCode(params);
        } while (de::contains(seen, code) && --genIterations > 0);

        seen.insert(code);

        string name = de::toString(i); // Would be code but baseSeed can change

        randomGroup->addChild(
            new SeparateShaderTest(ctx, name, name, numIterations, params, &SeparateShaderTest::testPipelineRendering));
    }

    TestCaseGroup *apiGroup = new TestCaseGroup(ctx, "api", "Program pipeline API tests");
    group->addChild(apiGroup);

    {
        // More or less random parameters. These shouldn't have much effect, so just
        // do a single sample.
        TestParams params = defaultParams;
        params.useUniform = true;
        apiGroup->addChild(new SeparateShaderTest(ctx, "current_program_priority",
                                                  "Test priority between current program and pipeline binding", 1,
                                                  params, &SeparateShaderTest::testCurrentProgPriority));
        apiGroup->addChild(new SeparateShaderTest(ctx, "active_program_uniform",
                                                  "Test that glUniform() affects a pipeline's active program", 1,
                                                  params, &SeparateShaderTest::testActiveProgramUniform));

        apiGroup->addChild(new SeparateShaderTest(ctx, "pipeline_programs",
                                                  "Test queries for programs in program pipeline stages", 1, params,
                                                  &SeparateShaderTest::testPipelineQueryPrograms));

        apiGroup->addChild(new SeparateShaderTest(ctx, "pipeline_active",
                                                  "Test query for active programs in a program pipeline", 1, params,
                                                  &SeparateShaderTest::testPipelineQueryActive));
    }

    return group;
}

TestCaseGroup *createGLESSeparateShaderTests(Context &ctx)
{
    TestCaseGroup *group = createCommonSeparateShaderTests(ctx);

    TestCaseGroup *interfaceMismatchGroup =
        new TestCaseGroup(ctx, "validation", "Negative program pipeline interface matching");
    group->addChild(interfaceMismatchGroup);

    {
        TestCaseGroup *es31Group = new TestCaseGroup(ctx, "es31", "GLSL ES 3.1 pipeline interface matching");
        gls::ShaderLibrary shaderLibrary(ctx.getTestContext(), ctx.getRenderContext(), ctx.getContextInfo());
        const std::vector<tcu::TestNode *> children =
            shaderLibrary.loadShaderFile("shaders/es31/separate_shader_validation.test");

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

        interfaceMismatchGroup->addChild(es31Group);
    }

    {
        TestCaseGroup *es32Group = new TestCaseGroup(ctx, "es32", "GLSL ES 3.2 pipeline interface matching");
        gls::ShaderLibrary shaderLibrary(ctx.getTestContext(), ctx.getRenderContext(), ctx.getContextInfo());
        const std::vector<tcu::TestNode *> children =
            shaderLibrary.loadShaderFile("shaders/es32/separate_shader_validation.test");

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

        interfaceMismatchGroup->addChild(es32Group);
    }

    return group;
}
} // namespace Functional
} // namespace gles31
} // namespace deqp
