/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.0 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 Texture access function tests.
 *//*--------------------------------------------------------------------*/

#include "es2fShaderTextureFunctionTests.hpp"
#include "glsShaderRenderCase.hpp"
#include "glsShaderLibrary.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluTexture.hpp"
#include "gluTextureUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuMatrix.hpp"
#include "tcuMatrixUtil.hpp"

#include <sstream>

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

namespace deqp
{
namespace gles2
{
namespace Functional
{

namespace
{

enum Function
{
    FUNCTION_TEXTURE = 0,  //!< texture(), textureOffset()
    FUNCTION_TEXTUREPROJ,  //!< textureProj(), textureProjOffset()
    FUNCTION_TEXTUREPROJ3, //!< textureProj(sampler2D, vec3)
    FUNCTION_TEXTURELOD,   // ...
    FUNCTION_TEXTUREPROJLOD,
    FUNCTION_TEXTUREPROJLOD3, //!< textureProjLod(sampler2D, vec3)

    FUNCTION_LAST
};

inline bool functionHasProj(Function function)
{
    return function == FUNCTION_TEXTUREPROJ || function == FUNCTION_TEXTUREPROJ3 ||
           function == FUNCTION_TEXTUREPROJLOD || function == FUNCTION_TEXTUREPROJLOD3;
}

inline bool functionHasLod(Function function)
{
    return function == FUNCTION_TEXTURELOD || function == FUNCTION_TEXTUREPROJLOD ||
           function == FUNCTION_TEXTUREPROJLOD3;
}

struct TextureLookupSpec
{
    Function function;

    tcu::Vec4 minCoord;
    tcu::Vec4 maxCoord;

    // Bias
    bool useBias;

    // Bias or Lod for *Lod* functions
    float minLodBias;
    float maxLodBias;

    TextureLookupSpec(void)
        : function(FUNCTION_LAST)
        , minCoord(0.0f)
        , maxCoord(1.0f)
        , useBias(false)
        , minLodBias(0.0f)
        , maxLodBias(0.0f)
    {
    }

    TextureLookupSpec(Function function_, const tcu::Vec4 &minCoord_, const tcu::Vec4 &maxCoord_, bool useBias_,
                      float minLodBias_, float maxLodBias_)
        : function(function_)
        , minCoord(minCoord_)
        , maxCoord(maxCoord_)
        , useBias(useBias_)
        , minLodBias(minLodBias_)
        , maxLodBias(maxLodBias_)
    {
    }
};

enum TextureType
{
    TEXTURETYPE_2D,
    TEXTURETYPE_CUBE_MAP,

    TEXTURETYPE_LAST
};

struct TextureSpec
{
    TextureType type; //!< Texture type (2D, cubemap, ...)
    uint32_t format;
    uint32_t dataType;
    int width;
    int height;
    int numLevels;
    tcu::Sampler sampler;

    TextureSpec(void) : type(TEXTURETYPE_LAST), format(GL_NONE), dataType(GL_NONE), width(0), height(0), numLevels(0)
    {
    }

    TextureSpec(TextureType type_, uint32_t format_, uint32_t dataType_, int width_, int height_, int numLevels_,
                const tcu::Sampler &sampler_)
        : type(type_)
        , format(format_)
        , dataType(dataType_)
        , width(width_)
        , height(height_)
        , numLevels(numLevels_)
        , sampler(sampler_)
    {
    }
};

struct TexLookupParams
{
    float lod;
    tcu::Vec4 scale;
    tcu::Vec4 bias;

    TexLookupParams(void) : lod(0.0f), scale(1.0f), bias(0.0f)
    {
    }
};

} // namespace

using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;

typedef void (*TexEvalFunc)(gls::ShaderEvalContext &c, const TexLookupParams &lookupParams);

inline Vec4 texture2D(const gls::ShaderEvalContext &c, float s, float t, float lod)
{
    return c.textures[0].tex2D->sample(c.textures[0].sampler, s, t, lod);
}
inline Vec4 textureCube(const gls::ShaderEvalContext &c, float s, float t, float r, float lod)
{
    return c.textures[0].texCube->sample(c.textures[0].sampler, s, t, r, lod);
}

// Eval functions.
static void evalTexture2D(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = texture2D(c, c.in[0].x(), c.in[0].y(), p.lod) * p.scale + p.bias;
}
static void evalTextureCube(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod) * p.scale + p.bias;
}

static void evalTexture2DBias(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = texture2D(c, c.in[0].x(), c.in[0].y(), p.lod + c.in[1].x()) * p.scale + p.bias;
}
static void evalTextureCubeBias(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), p.lod + c.in[1].x()) * p.scale + p.bias;
}

static void evalTexture2DProj3(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = texture2D(c, c.in[0].x() / c.in[0].z(), c.in[0].y() / c.in[0].z(), p.lod) * p.scale + p.bias;
}
static void evalTexture2DProj3Bias(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color =
        texture2D(c, c.in[0].x() / c.in[0].z(), c.in[0].y() / c.in[0].z(), p.lod + c.in[1].x()) * p.scale + p.bias;
}
static void evalTexture2DProj(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = texture2D(c, c.in[0].x() / c.in[0].w(), c.in[0].y() / c.in[0].w(), p.lod) * p.scale + p.bias;
}
static void evalTexture2DProjBias(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color =
        texture2D(c, c.in[0].x() / c.in[0].w(), c.in[0].y() / c.in[0].w(), p.lod + c.in[1].x()) * p.scale + p.bias;
}

static void evalTexture2DLod(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = texture2D(c, c.in[0].x(), c.in[0].y(), c.in[1].x()) * p.scale + p.bias;
}
static void evalTextureCubeLod(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = textureCube(c, c.in[0].x(), c.in[0].y(), c.in[0].z(), c.in[1].x()) * p.scale + p.bias;
}

static void evalTexture2DProjLod3(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = texture2D(c, c.in[0].x() / c.in[0].z(), c.in[0].y() / c.in[0].z(), c.in[1].x()) * p.scale + p.bias;
}
static void evalTexture2DProjLod(gls::ShaderEvalContext &c, const TexLookupParams &p)
{
    c.color = texture2D(c, c.in[0].x() / c.in[0].w(), c.in[0].y() / c.in[0].w(), c.in[1].x()) * p.scale + p.bias;
}

class TexLookupEvaluator : public gls::ShaderEvaluator
{
public:
    TexLookupEvaluator(TexEvalFunc evalFunc, const TexLookupParams &lookupParams)
        : m_evalFunc(evalFunc)
        , m_lookupParams(lookupParams)
    {
    }

    virtual void evaluate(gls::ShaderEvalContext &ctx)
    {
        m_evalFunc(ctx, m_lookupParams);
    }

private:
    TexEvalFunc m_evalFunc;
    const TexLookupParams &m_lookupParams;
};

class ShaderTextureFunctionCase : public gls::ShaderRenderCase
{
public:
    ShaderTextureFunctionCase(Context &context, const char *name, const char *desc, const TextureLookupSpec &lookup,
                              const TextureSpec &texture, TexEvalFunc evalFunc, bool isVertexCase);
    ~ShaderTextureFunctionCase(void);

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

protected:
    void setupUniforms(int programID, const tcu::Vec4 &constCoords);

private:
    void initTexture(void);
    void initShaderSources(void);

    TextureLookupSpec m_lookupSpec;
    TextureSpec m_textureSpec;

    TexLookupParams m_lookupParams;
    TexLookupEvaluator m_evaluator;

    glu::Texture2D *m_texture2D;
    glu::TextureCube *m_textureCube;
};

ShaderTextureFunctionCase::ShaderTextureFunctionCase(Context &context, const char *name, const char *desc,
                                                     const TextureLookupSpec &lookup, const TextureSpec &texture,
                                                     TexEvalFunc evalFunc, bool isVertexCase)
    : gls::ShaderRenderCase(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc,
                            isVertexCase, m_evaluator)
    , m_lookupSpec(lookup)
    , m_textureSpec(texture)
    , m_evaluator(evalFunc, m_lookupParams)
    , m_texture2D(DE_NULL)
    , m_textureCube(DE_NULL)
{
}

ShaderTextureFunctionCase::~ShaderTextureFunctionCase(void)
{
    delete m_texture2D;
    delete m_textureCube;
}

void ShaderTextureFunctionCase::init(void)
{
    if (m_isVertexCase)
    {
        const glw::Functions &gl = m_renderCtx.getFunctions();
        int numVertexUnits       = 0;
        gl.getIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &numVertexUnits);
        if (numVertexUnits < 1)
            throw tcu::NotSupportedError("Vertex shader texture access is not supported");
    }

    {
        // Base coord scale & bias
        Vec4 s = m_lookupSpec.maxCoord - m_lookupSpec.minCoord;
        Vec4 b = m_lookupSpec.minCoord;

        float baseCoordTrans[] = {s.x(),        0.0f,         0.f, b.x(),
                                  0.f,          s.y(),        0.f, b.y(),
                                  s.z() / 2.f,  -s.z() / 2.f, 0.f, s.z() / 2.f + b.z(),
                                  -s.w() / 2.f, s.w() / 2.f,  0.f, s.w() / 2.f + b.w()};

        m_userAttribTransforms.push_back(tcu::Mat4(baseCoordTrans));
    }

    if (functionHasLod(m_lookupSpec.function) || m_lookupSpec.useBias)
    {
        float s               = m_lookupSpec.maxLodBias - m_lookupSpec.minLodBias;
        float b               = m_lookupSpec.minLodBias;
        float lodCoordTrans[] = {s / 2.0f, s / 2.0f, 0.f,  b,    0.0f, 0.0f, 0.0f, 0.0f,
                                 0.0f,     0.0f,     0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};

        m_userAttribTransforms.push_back(tcu::Mat4(lodCoordTrans));
    }

    initShaderSources();
    initTexture();

    gls::ShaderRenderCase::init();
}

void ShaderTextureFunctionCase::initTexture(void)
{
    static const IVec4 texCubeSwz[] = {IVec4(0, 0, 1, 1), IVec4(1, 1, 0, 0), IVec4(0, 1, 0, 1),
                                       IVec4(1, 0, 1, 0), IVec4(0, 1, 1, 0), IVec4(1, 0, 0, 1)};
    DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(texCubeSwz) == tcu::CUBEFACE_LAST);

    tcu::TextureFormat texFmt      = glu::mapGLTransferFormat(m_textureSpec.format, m_textureSpec.dataType);
    tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(texFmt);
    tcu::IVec2 viewportSize        = getViewportSize();
    bool useProj                   = functionHasProj(m_lookupSpec.function) && !functionHasLod(m_lookupSpec.function);
    float proj = useProj ? 1.0f / m_lookupSpec.minCoord[m_lookupSpec.function == FUNCTION_TEXTUREPROJ3 ? 2 : 3] : 1.0f;

    switch (m_textureSpec.type)
    {
    case TEXTURETYPE_2D:
    {
        float cStep      = 1.0f / (float)de::max(1, m_textureSpec.numLevels - 1);
        Vec4 cScale      = fmtInfo.valueMax - fmtInfo.valueMin;
        Vec4 cBias       = fmtInfo.valueMin;
        int baseCellSize = de::min(m_textureSpec.width / 4, m_textureSpec.height / 4);

        m_texture2D = new glu::Texture2D(m_renderCtx, m_textureSpec.format, m_textureSpec.dataType, m_textureSpec.width,
                                         m_textureSpec.height);
        for (int level = 0; level < m_textureSpec.numLevels; level++)
        {
            float fA    = float(level) * cStep;
            float fB    = 1.0f - fA;
            Vec4 colorA = cBias + cScale * Vec4(fA, fB, fA, fB);
            Vec4 colorB = cBias + cScale * Vec4(fB, fA, fB, fA);

            m_texture2D->getRefTexture().allocLevel(level);
            tcu::fillWithGrid(m_texture2D->getRefTexture().getLevel(level), de::max(1, baseCellSize >> level), colorA,
                              colorB);
        }
        m_texture2D->upload();

        // Compute LOD.
        float dudx = (m_lookupSpec.maxCoord[0] - m_lookupSpec.minCoord[0]) * proj * (float)m_textureSpec.width /
                     (float)viewportSize[0];
        float dvdy = (m_lookupSpec.maxCoord[1] - m_lookupSpec.minCoord[1]) * proj * (float)m_textureSpec.height /
                     (float)viewportSize[1];
        m_lookupParams.lod =
            glu::TextureTestUtil::computeLodFromDerivates(glu::TextureTestUtil::LODMODE_EXACT, dudx, 0.0f, 0.0f, dvdy);

        // Append to texture list.
        m_textures.push_back(gls::TextureBinding(m_texture2D, m_textureSpec.sampler));
        break;
    }

    case TEXTURETYPE_CUBE_MAP:
    {
        float cStep      = 1.0f / (float)de::max(1, m_textureSpec.numLevels - 1);
        Vec4 cScale      = fmtInfo.valueMax - fmtInfo.valueMin;
        Vec4 cBias       = fmtInfo.valueMin;
        int baseCellSize = de::min(m_textureSpec.width / 4, m_textureSpec.height / 4);

        DE_ASSERT(m_textureSpec.width == m_textureSpec.height);
        m_textureCube =
            new glu::TextureCube(m_renderCtx, m_textureSpec.format, m_textureSpec.dataType, m_textureSpec.width);
        for (int level = 0; level < m_textureSpec.numLevels; level++)
        {
            float fA = float(level) * cStep;
            float fB = 1.0f - fA;
            Vec2 f(fA, fB);

            for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
            {
                const IVec4 &swzA = texCubeSwz[face];
                IVec4 swzB        = 1 - swzA;
                Vec4 colorA       = cBias + cScale * f.swizzle(swzA[0], swzA[1], swzA[2], swzA[3]);
                Vec4 colorB       = cBias + cScale * f.swizzle(swzB[0], swzB[1], swzB[2], swzB[3]);

                m_textureCube->getRefTexture().allocLevel((tcu::CubeFace)face, level);
                tcu::fillWithGrid(m_textureCube->getRefTexture().getLevelFace(level, (tcu::CubeFace)face),
                                  de::max(1, baseCellSize >> level), colorA, colorB);
            }
        }
        m_textureCube->upload();

        // Compute LOD \note Assumes that only single side is accessed and R is constant major axis.
        DE_ASSERT(de::abs(m_lookupSpec.minCoord[2] - m_lookupSpec.maxCoord[2]) < 0.005);
        DE_ASSERT(de::abs(m_lookupSpec.minCoord[0]) < de::abs(m_lookupSpec.minCoord[2]) &&
                  de::abs(m_lookupSpec.maxCoord[0]) < de::abs(m_lookupSpec.minCoord[2]));
        DE_ASSERT(de::abs(m_lookupSpec.minCoord[1]) < de::abs(m_lookupSpec.minCoord[2]) &&
                  de::abs(m_lookupSpec.maxCoord[1]) < de::abs(m_lookupSpec.minCoord[2]));

        tcu::CubeFaceFloatCoords c00 = tcu::getCubeFaceCoords(
            Vec3(m_lookupSpec.minCoord[0] * proj, m_lookupSpec.minCoord[1] * proj, m_lookupSpec.minCoord[2] * proj));
        tcu::CubeFaceFloatCoords c10 = tcu::getCubeFaceCoords(
            Vec3(m_lookupSpec.maxCoord[0] * proj, m_lookupSpec.minCoord[1] * proj, m_lookupSpec.minCoord[2] * proj));
        tcu::CubeFaceFloatCoords c01 = tcu::getCubeFaceCoords(
            Vec3(m_lookupSpec.minCoord[0] * proj, m_lookupSpec.maxCoord[1] * proj, m_lookupSpec.minCoord[2] * proj));
        float dudx = (c10.s - c00.s) * (float)m_textureSpec.width / (float)viewportSize[0];
        float dvdy = (c01.t - c00.t) * (float)m_textureSpec.height / (float)viewportSize[1];

        m_lookupParams.lod =
            glu::TextureTestUtil::computeLodFromDerivates(glu::TextureTestUtil::LODMODE_EXACT, dudx, 0.0f, 0.0f, dvdy);

        m_textures.push_back(gls::TextureBinding(m_textureCube, m_textureSpec.sampler));
        break;
    }

    default:
        DE_ASSERT(false);
    }

    // Set lookup scale & bias
    m_lookupParams.scale = fmtInfo.lookupScale;
    m_lookupParams.bias  = fmtInfo.lookupBias;
}

void ShaderTextureFunctionCase::initShaderSources(void)
{
    Function function = m_lookupSpec.function;
    bool isVtxCase    = m_isVertexCase;
    bool isProj       = functionHasProj(function);
    bool is2DProj4    = m_textureSpec.type == TEXTURETYPE_2D &&
                     (function == FUNCTION_TEXTUREPROJ || function == FUNCTION_TEXTUREPROJLOD);
    bool hasLodBias           = functionHasLod(m_lookupSpec.function) || m_lookupSpec.useBias;
    int texCoordComps         = m_textureSpec.type == TEXTURETYPE_2D ? 2 : 3;
    int extraCoordComps       = isProj ? (is2DProj4 ? 2 : 1) : 0;
    glu::DataType coordType   = glu::getDataTypeFloatVec(texCoordComps + extraCoordComps);
    glu::Precision coordPrec  = glu::PRECISION_MEDIUMP;
    const char *coordTypeName = glu::getDataTypeName(coordType);
    const char *coordPrecName = glu::getPrecisionName(coordPrec);
    tcu::TextureFormat texFmt = glu::mapGLTransferFormat(m_textureSpec.format, m_textureSpec.dataType);
    glu::DataType samplerType = glu::TYPE_LAST;
    const char *baseFuncName  = m_textureSpec.type == TEXTURETYPE_2D ? "texture2D" : "textureCube";
    const char *funcExt       = DE_NULL;

    switch (m_textureSpec.type)
    {
    case TEXTURETYPE_2D:
        samplerType = glu::getSampler2DType(texFmt);
        break;
    case TEXTURETYPE_CUBE_MAP:
        samplerType = glu::getSamplerCubeType(texFmt);
        break;
    default:
        DE_ASSERT(false);
    }

    switch (m_lookupSpec.function)
    {
    case FUNCTION_TEXTURE:
        funcExt = "";
        break;
    case FUNCTION_TEXTUREPROJ:
        funcExt = "Proj";
        break;
    case FUNCTION_TEXTUREPROJ3:
        funcExt = "Proj";
        break;
    case FUNCTION_TEXTURELOD:
        funcExt = "Lod";
        break;
    case FUNCTION_TEXTUREPROJLOD:
        funcExt = "ProjLod";
        break;
    case FUNCTION_TEXTUREPROJLOD3:
        funcExt = "ProjLod";
        break;
    default:
        DE_ASSERT(false);
    }

    std::ostringstream vert;
    std::ostringstream frag;
    std::ostringstream &op = isVtxCase ? vert : frag;

    vert << "attribute highp vec4 a_position;\n"
         << "attribute " << coordPrecName << " " << coordTypeName << " a_in0;\n";

    if (hasLodBias)
        vert << "attribute " << coordPrecName << " float a_in1;\n";

    if (isVtxCase)
    {
        vert << "varying mediump vec4 v_color;\n";
        frag << "varying mediump vec4 v_color;\n";
    }
    else
    {
        vert << "varying " << coordPrecName << " " << coordTypeName << " v_texCoord;\n";
        frag << "varying " << coordPrecName << " " << coordTypeName << " v_texCoord;\n";

        if (hasLodBias)
        {
            vert << "varying " << coordPrecName << " float v_lodBias;\n";
            frag << "varying " << coordPrecName << " float v_lodBias;\n";
        }
    }

    // Uniforms
    op << "uniform lowp " << glu::getDataTypeName(samplerType) << " u_sampler;\n";

    vert << "\nvoid main()\n{\n"
         << "\tgl_Position = a_position;\n";
    frag << "\nvoid main()\n{\n";

    if (isVtxCase)
        vert << "\tv_color = ";
    else
        frag << "\tgl_FragColor = ";

    // Op.
    {
        const char *texCoord = isVtxCase ? "a_in0" : "v_texCoord";
        const char *lodBias  = isVtxCase ? "a_in1" : "v_lodBias";

        op << baseFuncName << funcExt;
        op << "(u_sampler, " << texCoord;

        if (functionHasLod(function) || m_lookupSpec.useBias)
            op << ", " << lodBias;

        op << ");\n";
    }

    if (isVtxCase)
        frag << "\tgl_FragColor = v_color;\n";
    else
    {
        vert << "\tv_texCoord = a_in0;\n";

        if (hasLodBias)
            vert << "\tv_lodBias = a_in1;\n";
    }

    vert << "}\n";
    frag << "}\n";

    m_vertShaderSource = vert.str();
    m_fragShaderSource = frag.str();
}

void ShaderTextureFunctionCase::deinit(void)
{
    gls::ShaderRenderCase::deinit();

    delete m_texture2D;
    delete m_textureCube;

    m_texture2D   = DE_NULL;
    m_textureCube = DE_NULL;
}

void ShaderTextureFunctionCase::setupUniforms(int programID, const tcu::Vec4 &)
{
    const glw::Functions &gl = m_renderCtx.getFunctions();
    gl.uniform1i(gl.getUniformLocation(programID, "u_sampler"), 0);
}

ShaderTextureFunctionTests::ShaderTextureFunctionTests(Context &context)
    : TestCaseGroup(context, "texture_functions", "Texture Access Function Tests")
{
}

ShaderTextureFunctionTests::~ShaderTextureFunctionTests(void)
{
}

struct TexFuncCaseSpec
{
    const char *name;
    TextureLookupSpec lookupSpec;
    TextureSpec texSpec;
    TexEvalFunc evalFunc;
};

#define CASE_SPEC(NAME, FUNC, MINCOORD, MAXCOORD, USEBIAS, MINLOD, MAXLOD, TEXSPEC, EVALFUNC)          \
    {                                                                                                  \
        #NAME, TextureLookupSpec(FUNC, MINCOORD, MAXCOORD, USEBIAS, MINLOD, MAXLOD), TEXSPEC, EVALFUNC \
    }

static void createCaseGroup(TestCaseGroup *parent, const char *groupName, const char *groupDesc,
                            const TexFuncCaseSpec *cases, int numCases, bool isVertex)
{
    tcu::TestCaseGroup *group = new tcu::TestCaseGroup(parent->getTestContext(), groupName, groupDesc);
    parent->addChild(group);

    for (int ndx = 0; ndx < numCases; ndx++)
        group->addChild(new ShaderTextureFunctionCase(parent->getContext(), cases[ndx].name, "", cases[ndx].lookupSpec,
                                                      cases[ndx].texSpec, cases[ndx].evalFunc, isVertex));
}

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

    // Samplers
    static const tcu::Sampler samplerLinearNoMipmap(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
                                                    tcu::Sampler::REPEAT_GL, tcu::Sampler::LINEAR,
                                                    tcu::Sampler::LINEAR);
    static tcu::Sampler samplerLinearMipmap(tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
                                            tcu::Sampler::LINEAR_MIPMAP_NEAREST, tcu::Sampler::LINEAR);

    samplerLinearMipmap.seamlessCubeMap = glu::IsES3Compatible(gl);

    // Default textures.
    //                                                Type            Format        DataType            W        H        L    Sampler
    static const TextureSpec tex2D(TEXTURETYPE_2D, GL_RGBA, GL_UNSIGNED_BYTE, 256, 256, 1, samplerLinearNoMipmap);
    static const TextureSpec tex2DMipmap(TEXTURETYPE_2D, GL_RGBA, GL_UNSIGNED_BYTE, 256, 256, 9, samplerLinearMipmap);

    static const TextureSpec texCube(TEXTURETYPE_CUBE_MAP, GL_RGBA, GL_UNSIGNED_BYTE, 256, 256, 1,
                                     samplerLinearNoMipmap);
    static const TextureSpec texCubeMipmap(TEXTURETYPE_CUBE_MAP, GL_RGBA, GL_UNSIGNED_BYTE, 256, 256, 9,
                                           samplerLinearMipmap);

    // Vertex cases
    static const TexFuncCaseSpec vertexCases[] = {
        //          Name                        Function                    MinCoord                            MaxCoord                            Bias?    MinLod    MaxLod    Texture            EvalFunc
        CASE_SPEC(texture2d, FUNCTION_TEXTURE, Vec4(-0.2f, -0.4f, 0.0f, 0.0f), Vec4(1.5f, 2.3f, 0.0f, 0.0f), false,
                  0.0f, 0.0f, tex2D, evalTexture2D),
        //        CASE_SPEC(texture2d_bias, FUNCTION_TEXTURE, Vec4(-0.2f, -0.4f,  0.0f,  0.0f), Vec4( 1.5f,  2.3f,  0.0f,  0.0f), true, -2.0f, 2.0f, tex2D, evalTexture2DBias),
        CASE_SPEC(texture2dproj_vec3, FUNCTION_TEXTUREPROJ3, Vec4(-0.3f, -0.6f, 1.5f, 0.0f),
                  Vec4(2.25f, 3.45f, 1.5f, 0.0f), false, 0.0f, 0.0f, tex2D, evalTexture2DProj3),
        CASE_SPEC(texture2dproj_vec4, FUNCTION_TEXTUREPROJ, Vec4(-0.3f, -0.6f, 0.0f, 1.5f),
                  Vec4(2.25f, 3.45f, 0.0f, 1.5f), false, 0.0f, 0.0f, tex2D, evalTexture2DProj),
        CASE_SPEC(texture2dlod, FUNCTION_TEXTURELOD, Vec4(-0.2f, -0.4f, 0.0f, 0.0f), Vec4(1.5f, 2.3f, 0.0f, 0.0f),
                  false, -1.0f, 9.0f, tex2DMipmap, evalTexture2DLod),
        //        CASE_SPEC(texture2dproj_vec3_bias, FUNCTION_TEXTUREPROJ3, Vec4(-0.3f, -0.6f,  1.5f,  0.0f), Vec4(2.25f, 3.45f,  1.5f,  0.0f), true, -2.0f, 2.0f, tex2D, evalTexture2DProj3Bias),
        //        CASE_SPEC(texture2dproj_vec4_bias, FUNCTION_TEXTUREPROJ, Vec4(-0.3f, -0.6f,  0.0f,  1.5f), Vec4(2.25f, 3.45f,  0.0f,  1.5f), true, -2.0f, 2.0f, tex2D, evalTexture2DProjBias),
        CASE_SPEC(texture2dprojlod_vec3, FUNCTION_TEXTUREPROJLOD3, Vec4(-0.3f, -0.6f, 1.5f, 0.0f),
                  Vec4(2.25f, 3.45f, 1.5f, 0.0f), false, -1.0f, 9.0f, tex2D, evalTexture2DProjLod3),
        CASE_SPEC(texture2dprojlod_vec4, FUNCTION_TEXTUREPROJLOD, Vec4(-0.3f, -0.6f, 0.0f, 1.5f),
                  Vec4(2.25f, 3.45f, 0.0f, 1.5f), false, -1.0f, 9.0f, tex2D, evalTexture2DProjLod),
        CASE_SPEC(texturecube, FUNCTION_TEXTURE, Vec4(-1.0f, -1.0f, 1.01f, 0.0f), Vec4(1.0f, 1.0f, 1.01f, 0.0f), false,
                  0.0f, 0.0f, texCube, evalTextureCube),
        //        CASE_SPEC(texturecube_bias, FUNCTION_TEXTURE, Vec4(-1.0f, -1.0f, -1.01f,  0.0f), Vec4( 1.0f,  1.0f, -1.01f,  0.0f), true, -2.0f, 2.0f, texCube, evalTextureCubeBias),
        CASE_SPEC(texturecubelod, FUNCTION_TEXTURELOD, Vec4(-1.0f, -1.0f, 1.01f, 0.0f), Vec4(1.0f, 1.0f, 1.01f, 0.0f),
                  false, -1.0f, 9.0f, texCubeMipmap, evalTextureCubeLod),
    };
    createCaseGroup(this, "vertex", "Vertex Shader Texture Lookups", &vertexCases[0], DE_LENGTH_OF_ARRAY(vertexCases),
                    true);

    // Fragment cases
    static const TexFuncCaseSpec fragmentCases[] = {
        //          Name                        Function                MinCoord                            MaxCoord                            Bias?    MinLod    MaxLod    Texture            EvalFunc
        CASE_SPEC(texture2d, FUNCTION_TEXTURE, Vec4(-0.2f, -0.4f, 0.0f, 0.0f), Vec4(1.5f, 2.3f, 0.0f, 0.0f), false,
                  0.0f, 0.0f, tex2DMipmap, evalTexture2D),
        CASE_SPEC(texture2d_bias, FUNCTION_TEXTURE, Vec4(-0.2f, -0.4f, 0.0f, 0.0f), Vec4(1.5f, 2.3f, 0.0f, 0.0f), true,
                  -2.0f, 2.0f, tex2DMipmap, evalTexture2DBias),
        CASE_SPEC(texture2dproj_vec3, FUNCTION_TEXTUREPROJ3, Vec4(-0.3f, -0.6f, 1.5f, 0.0f),
                  Vec4(2.25f, 3.45f, 1.5f, 0.0f), false, 0.0f, 0.0f, tex2DMipmap, evalTexture2DProj3),
        CASE_SPEC(texture2dproj_vec4, FUNCTION_TEXTUREPROJ, Vec4(-0.3f, -0.6f, 0.0f, 1.5f),
                  Vec4(2.25f, 3.45f, 0.0f, 1.5f), false, 0.0f, 0.0f, tex2DMipmap, evalTexture2DProj),
        CASE_SPEC(texture2dproj_vec3_bias, FUNCTION_TEXTUREPROJ3, Vec4(-0.3f, -0.6f, 1.5f, 0.0f),
                  Vec4(2.25f, 3.45f, 1.5f, 0.0f), true, -2.0f, 2.0f, tex2DMipmap, evalTexture2DProj3Bias),
        CASE_SPEC(texture2dproj_vec4_bias, FUNCTION_TEXTUREPROJ, Vec4(-0.3f, -0.6f, 0.0f, 1.5f),
                  Vec4(2.25f, 3.45f, 0.0f, 1.5f), true, -2.0f, 2.0f, tex2DMipmap, evalTexture2DProjBias),
        CASE_SPEC(texturecube, FUNCTION_TEXTURE, Vec4(-1.0f, -1.0f, 1.01f, 0.0f), Vec4(1.0f, 1.0f, 1.01f, 0.0f), false,
                  0.0f, 0.0f, texCubeMipmap, evalTextureCube),
        CASE_SPEC(texturecube_bias, FUNCTION_TEXTURE, Vec4(-1.0f, -1.0f, -1.01f, 0.0f), Vec4(1.0f, 1.0f, -1.01f, 0.0f),
                  true, -2.0f, 2.0f, texCubeMipmap, evalTextureCubeBias)};
    createCaseGroup(this, "fragment", "Fragment Shader Texture Lookups", &fragmentCases[0],
                    DE_LENGTH_OF_ARRAY(fragmentCases), false);

    // Negative cases.
    {
        gls::ShaderLibrary library(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
        std::vector<tcu::TestNode *> negativeCases = library.loadShaderFile("shaders/invalid_texture_functions.test");

        tcu::TestCaseGroup *group =
            new tcu::TestCaseGroup(m_testCtx, "invalid", "Invalid texture function usage", negativeCases);
        addChild(group);
    }
}

} // namespace Functional
} // namespace gles2
} // namespace deqp
