/*-------------------------------------------------------------------------
 * 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 GLSL textureGather[Offset[s]] tests.
 *//*--------------------------------------------------------------------*/

#include "es31fTextureGatherTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluTexture.hpp"
#include "gluDrawUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuTexLookupVerifier.hpp"
#include "tcuTexCompareVerifier.hpp"
#include "tcuCommandLine.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include "deString.h"

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

using de::MovePtr;
using glu::ShaderProgram;
using tcu::ConstPixelBufferAccess;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::PixelBufferAccess;
using tcu::TestLog;
using tcu::UVec4;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;

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

namespace deqp
{

using glu::TextureTestUtil::TextureType;
using glu::TextureTestUtil::TEXTURETYPE_2D;
using glu::TextureTestUtil::TEXTURETYPE_2D_ARRAY;
using glu::TextureTestUtil::TEXTURETYPE_CUBE;

namespace gles31
{
namespace Functional
{

namespace
{

static std::string specializeShader(Context &context, const char *code)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(context.getRenderContext().getType());
    std::map<std::string, std::string> specializationMap;

    specializationMap["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);

    if (glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(context.getRenderContext().getType(), glu::ApiType::core(4, 5)))
        specializationMap["GPU_SHADER5_REQUIRE"] = "";
    else
        specializationMap["GPU_SHADER5_REQUIRE"] = "#extension GL_EXT_gpu_shader5 : require";

    return tcu::StringTemplate(code).specialize(specializationMap);
}

// Round-to-zero int division, because pre-c++11 it's somewhat implementation-defined for negative values.
static inline int divRoundToZero(int a, int b)
{
    return de::abs(a) / de::abs(b) * deSign32(a) * deSign32(b);
}

static void fillWithRandomColorTiles(const PixelBufferAccess &dst, const Vec4 &minVal, const Vec4 &maxVal,
                                     uint32_t seed)
{
    const int numCols = dst.getWidth() >= 7 ? 7 : dst.getWidth();
    const int numRows = dst.getHeight() >= 5 ? 5 : dst.getHeight();
    de::Random rnd(seed);

    for (int slice = 0; slice < dst.getDepth(); slice++)
        for (int row = 0; row < numRows; row++)
            for (int col = 0; col < numCols; col++)
            {
                const int yBegin = (row + 0) * dst.getHeight() / numRows;
                const int yEnd   = (row + 1) * dst.getHeight() / numRows;
                const int xBegin = (col + 0) * dst.getWidth() / numCols;
                const int xEnd   = (col + 1) * dst.getWidth() / numCols;
                Vec4 color;
                for (int i = 0; i < 4; i++)
                    color[i] = rnd.getFloat(minVal[i], maxVal[i]);
                tcu::clear(tcu::getSubregion(dst, xBegin, yBegin, slice, xEnd - xBegin, yEnd - yBegin, 1), color);
            }
}

static inline bool isDepthFormat(const tcu::TextureFormat &fmt)
{
    return fmt.order == tcu::TextureFormat::D || fmt.order == tcu::TextureFormat::DS;
}

static inline bool isUnormFormatType(tcu::TextureFormat::ChannelType type)
{
    return type == tcu::TextureFormat::UNORM_INT8 || type == tcu::TextureFormat::UNORM_INT16 ||
           type == tcu::TextureFormat::UNORM_INT32;
}

static inline bool isSIntFormatType(tcu::TextureFormat::ChannelType type)
{
    return type == tcu::TextureFormat::SIGNED_INT8 || type == tcu::TextureFormat::SIGNED_INT16 ||
           type == tcu::TextureFormat::SIGNED_INT32;
}

static inline bool isUIntFormatType(tcu::TextureFormat::ChannelType type)
{
    return type == tcu::TextureFormat::UNSIGNED_INT8 || type == tcu::TextureFormat::UNSIGNED_INT16 ||
           type == tcu::TextureFormat::UNSIGNED_INT32;
}

static tcu::TextureLevel getPixels(const glu::RenderContext &renderCtx, const IVec2 &size,
                                   const tcu::TextureFormat &colorBufferFormat)
{
    tcu::TextureLevel result(colorBufferFormat, size.x(), size.y());

    // only a few pixel formats are guaranteed to be valid targets for readPixels, convert the rest
    if (colorBufferFormat.order == tcu::TextureFormat::RGBA &&
        (colorBufferFormat.type == tcu::TextureFormat::UNORM_INT8 ||
         colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT32 ||
         colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT32))
    {
        // valid as is
        glu::readPixels(renderCtx, 0, 0, result.getAccess());
    }
    else if (colorBufferFormat.order == tcu::TextureFormat::RGBA &&
             (isSIntFormatType(colorBufferFormat.type) || isUIntFormatType(colorBufferFormat.type)))
    {
        // signed and unsigned integers must be read using 32-bit values
        const bool isSigned = isSIntFormatType(colorBufferFormat.type);
        tcu::TextureLevel readResult(
            tcu::TextureFormat(tcu::TextureFormat::RGBA,
                               (isSigned) ? (tcu::TextureFormat::SIGNED_INT32) : (tcu::TextureFormat::UNSIGNED_INT32)),
            size.x(), size.y());

        glu::readPixels(renderCtx, 0, 0, readResult.getAccess());
        tcu::copy(result.getAccess(), readResult.getAccess());
    }
    else
    {
        // unreadable format
        DE_ASSERT(false);
    }

    return result;
}

enum TextureSwizzleComponent
{
    TEXTURESWIZZLECOMPONENT_R = 0,
    TEXTURESWIZZLECOMPONENT_G,
    TEXTURESWIZZLECOMPONENT_B,
    TEXTURESWIZZLECOMPONENT_A,
    TEXTURESWIZZLECOMPONENT_ZERO,
    TEXTURESWIZZLECOMPONENT_ONE,

    TEXTURESWIZZLECOMPONENT_LAST
};

static std::ostream &operator<<(std::ostream &stream, TextureSwizzleComponent comp)
{
    switch (comp)
    {
    case TEXTURESWIZZLECOMPONENT_R:
        return stream << "RED";
    case TEXTURESWIZZLECOMPONENT_G:
        return stream << "GREEN";
    case TEXTURESWIZZLECOMPONENT_B:
        return stream << "BLUE";
    case TEXTURESWIZZLECOMPONENT_A:
        return stream << "ALPHA";
    case TEXTURESWIZZLECOMPONENT_ZERO:
        return stream << "ZERO";
    case TEXTURESWIZZLECOMPONENT_ONE:
        return stream << "ONE";
    default:
        DE_ASSERT(false);
        return stream;
    }
}

struct MaybeTextureSwizzle
{
public:
    static MaybeTextureSwizzle createNoneTextureSwizzle(void);
    static MaybeTextureSwizzle createSomeTextureSwizzle(void);

    bool isSome(void) const;
    bool isNone(void) const;
    bool isIdentitySwizzle(void) const;

    tcu::Vector<TextureSwizzleComponent, 4> &getSwizzle(void);
    const tcu::Vector<TextureSwizzleComponent, 4> &getSwizzle(void) const;

private:
    MaybeTextureSwizzle(void);

    tcu::Vector<TextureSwizzleComponent, 4> m_swizzle;
    bool m_isSome;
};

static std::ostream &operator<<(std::ostream &stream, const MaybeTextureSwizzle &comp)
{
    if (comp.isNone())
        stream << "[default swizzle state]";
    else
        stream << "(" << comp.getSwizzle()[0] << ", " << comp.getSwizzle()[1] << ", " << comp.getSwizzle()[2] << ", "
               << comp.getSwizzle()[3] << ")";

    return stream;
}

MaybeTextureSwizzle MaybeTextureSwizzle::createNoneTextureSwizzle(void)
{
    MaybeTextureSwizzle swizzle;

    swizzle.m_swizzle[0] = TEXTURESWIZZLECOMPONENT_LAST;
    swizzle.m_swizzle[1] = TEXTURESWIZZLECOMPONENT_LAST;
    swizzle.m_swizzle[2] = TEXTURESWIZZLECOMPONENT_LAST;
    swizzle.m_swizzle[3] = TEXTURESWIZZLECOMPONENT_LAST;
    swizzle.m_isSome     = false;

    return swizzle;
}

MaybeTextureSwizzle MaybeTextureSwizzle::createSomeTextureSwizzle(void)
{
    MaybeTextureSwizzle swizzle;

    swizzle.m_swizzle[0] = TEXTURESWIZZLECOMPONENT_R;
    swizzle.m_swizzle[1] = TEXTURESWIZZLECOMPONENT_G;
    swizzle.m_swizzle[2] = TEXTURESWIZZLECOMPONENT_B;
    swizzle.m_swizzle[3] = TEXTURESWIZZLECOMPONENT_A;
    swizzle.m_isSome     = true;

    return swizzle;
}

bool MaybeTextureSwizzle::isSome(void) const
{
    return m_isSome;
}

bool MaybeTextureSwizzle::isNone(void) const
{
    return !m_isSome;
}

bool MaybeTextureSwizzle::isIdentitySwizzle(void) const
{
    return m_isSome && m_swizzle[0] == TEXTURESWIZZLECOMPONENT_R && m_swizzle[1] == TEXTURESWIZZLECOMPONENT_G &&
           m_swizzle[2] == TEXTURESWIZZLECOMPONENT_B && m_swizzle[3] == TEXTURESWIZZLECOMPONENT_A;
}

tcu::Vector<TextureSwizzleComponent, 4> &MaybeTextureSwizzle::getSwizzle(void)
{
    return m_swizzle;
}

const tcu::Vector<TextureSwizzleComponent, 4> &MaybeTextureSwizzle::getSwizzle(void) const
{
    return m_swizzle;
}

MaybeTextureSwizzle::MaybeTextureSwizzle(void)
    : m_swizzle(TEXTURESWIZZLECOMPONENT_LAST, TEXTURESWIZZLECOMPONENT_LAST, TEXTURESWIZZLECOMPONENT_LAST,
                TEXTURESWIZZLECOMPONENT_LAST)
    , m_isSome(false)
{
}

static uint32_t getGLTextureSwizzleComponent(TextureSwizzleComponent c)
{
    switch (c)
    {
    case TEXTURESWIZZLECOMPONENT_R:
        return GL_RED;
    case TEXTURESWIZZLECOMPONENT_G:
        return GL_GREEN;
    case TEXTURESWIZZLECOMPONENT_B:
        return GL_BLUE;
    case TEXTURESWIZZLECOMPONENT_A:
        return GL_ALPHA;
    case TEXTURESWIZZLECOMPONENT_ZERO:
        return GL_ZERO;
    case TEXTURESWIZZLECOMPONENT_ONE:
        return GL_ONE;
    default:
        DE_ASSERT(false);
        return (uint32_t)-1;
    }
}

template <typename T>
static inline T swizzleColorChannel(const tcu::Vector<T, 4> &src, TextureSwizzleComponent swizzle)
{
    switch (swizzle)
    {
    case TEXTURESWIZZLECOMPONENT_R:
        return src[0];
    case TEXTURESWIZZLECOMPONENT_G:
        return src[1];
    case TEXTURESWIZZLECOMPONENT_B:
        return src[2];
    case TEXTURESWIZZLECOMPONENT_A:
        return src[3];
    case TEXTURESWIZZLECOMPONENT_ZERO:
        return (T)0;
    case TEXTURESWIZZLECOMPONENT_ONE:
        return (T)1;
    default:
        DE_ASSERT(false);
        return (T)-1;
    }
}

template <typename T>
static inline tcu::Vector<T, 4> swizzleColor(const tcu::Vector<T, 4> &src, const MaybeTextureSwizzle &swizzle)
{
    DE_ASSERT(swizzle.isSome());

    tcu::Vector<T, 4> result;
    for (int i = 0; i < 4; i++)
        result[i] = swizzleColorChannel(src, swizzle.getSwizzle()[i]);
    return result;
}

template <typename T>
static void swizzlePixels(const PixelBufferAccess &dst, const ConstPixelBufferAccess &src,
                          const MaybeTextureSwizzle &swizzle)
{
    DE_ASSERT(dst.getWidth() == src.getWidth() && dst.getHeight() == src.getHeight() &&
              dst.getDepth() == src.getDepth());
    for (int z = 0; z < src.getDepth(); z++)
        for (int y = 0; y < src.getHeight(); y++)
            for (int x = 0; x < src.getWidth(); x++)
                dst.setPixel(swizzleColor(src.getPixelT<T>(x, y, z), swizzle), x, y, z);
}

static void swizzlePixels(const PixelBufferAccess &dst, const ConstPixelBufferAccess &src,
                          const MaybeTextureSwizzle &swizzle)
{
    if (isDepthFormat(dst.getFormat()))
        DE_ASSERT(swizzle.isNone() || swizzle.isIdentitySwizzle());

    if (swizzle.isNone() || swizzle.isIdentitySwizzle())
        tcu::copy(dst, src);
    else if (isUnormFormatType(dst.getFormat().type))
        swizzlePixels<float>(dst, src, swizzle);
    else if (isUIntFormatType(dst.getFormat().type))
        swizzlePixels<uint32_t>(dst, src, swizzle);
    else if (isSIntFormatType(dst.getFormat().type))
        swizzlePixels<int32_t>(dst, src, swizzle);
    else
        DE_ASSERT(false);
}

static void swizzleTexture(tcu::Texture2D &dst, const tcu::Texture2D &src, const MaybeTextureSwizzle &swizzle)
{
    dst = tcu::Texture2D(src.getFormat(), src.getWidth(), src.getHeight());
    for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
    {
        if (src.isLevelEmpty(levelNdx))
            continue;
        dst.allocLevel(levelNdx);
        swizzlePixels(dst.getLevel(levelNdx), src.getLevel(levelNdx), swizzle);
    }
}

static void swizzleTexture(tcu::Texture2DArray &dst, const tcu::Texture2DArray &src, const MaybeTextureSwizzle &swizzle)
{
    dst = tcu::Texture2DArray(src.getFormat(), src.getWidth(), src.getHeight(), src.getNumLayers());
    for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
    {
        if (src.isLevelEmpty(levelNdx))
            continue;
        dst.allocLevel(levelNdx);
        swizzlePixels(dst.getLevel(levelNdx), src.getLevel(levelNdx), swizzle);
    }
}

static void swizzleTexture(tcu::TextureCube &dst, const tcu::TextureCube &src, const MaybeTextureSwizzle &swizzle)
{
    dst = tcu::TextureCube(src.getFormat(), src.getSize());
    for (int faceI = 0; faceI < tcu::CUBEFACE_LAST; faceI++)
    {
        const tcu::CubeFace face = (tcu::CubeFace)faceI;
        for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
        {
            if (src.isLevelEmpty(face, levelNdx))
                continue;
            dst.allocLevel(face, levelNdx);
            swizzlePixels(dst.getLevelFace(levelNdx, face), src.getLevelFace(levelNdx, face), swizzle);
        }
    }
}

static tcu::Texture2DView getOneLevelSubView(const tcu::Texture2DView &view, int level)
{
    return tcu::Texture2DView(1, view.getLevels() + level);
}

static tcu::Texture2DArrayView getOneLevelSubView(const tcu::Texture2DArrayView &view, int level)
{
    return tcu::Texture2DArrayView(1, view.getLevels() + level);
}

static tcu::TextureCubeView getOneLevelSubView(const tcu::TextureCubeView &view, int level)
{
    const tcu::ConstPixelBufferAccess *levels[tcu::CUBEFACE_LAST];

    for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
        levels[face] = view.getFaceLevels((tcu::CubeFace)face) + level;

    return tcu::TextureCubeView(1, levels);
}

class PixelOffsets
{
public:
    virtual void operator()(const IVec2 &pixCoord, IVec2 (&dst)[4]) const = 0;
    virtual ~PixelOffsets(void)
    {
    }
};

class MultiplePixelOffsets : public PixelOffsets
{
public:
    MultiplePixelOffsets(const IVec2 &a, const IVec2 &b, const IVec2 &c, const IVec2 &d)
    {
        m_offsets[0] = a;
        m_offsets[1] = b;
        m_offsets[2] = c;
        m_offsets[3] = d;
    }

    void operator()(const IVec2 & /* pixCoord */, IVec2 (&dst)[4]) const
    {
        for (int i = 0; i < DE_LENGTH_OF_ARRAY(dst); i++)
            dst[i] = m_offsets[i];
    }

private:
    IVec2 m_offsets[4];
};

class SinglePixelOffsets : public MultiplePixelOffsets
{
public:
    SinglePixelOffsets(const IVec2 &offset)
        : MultiplePixelOffsets(offset + IVec2(0, 1), offset + IVec2(1, 1), offset + IVec2(1, 0), offset + IVec2(0, 0))
    {
    }
};

class DynamicSinglePixelOffsets : public PixelOffsets
{
public:
    DynamicSinglePixelOffsets(const IVec2 &offsetRange) : m_offsetRange(offsetRange)
    {
    }

    void operator()(const IVec2 &pixCoord, IVec2 (&dst)[4]) const
    {
        const int offsetRangeSize = m_offsetRange.y() - m_offsetRange.x() + 1;
        SinglePixelOffsets(tcu::mod(pixCoord.swizzle(1, 0), IVec2(offsetRangeSize)) + m_offsetRange.x())(IVec2(), dst);
    }

private:
    IVec2 m_offsetRange;
};

template <typename T>
static inline T triQuadInterpolate(const T (&values)[4], float xFactor, float yFactor)
{
    if (xFactor + yFactor < 1.0f)
        return values[0] + (values[2] - values[0]) * xFactor + (values[1] - values[0]) * yFactor;
    else
        return values[3] + (values[1] - values[3]) * (1.0f - xFactor) + (values[2] - values[3]) * (1.0f - yFactor);
}

template <int N>
static inline void computeTexCoordVecs(const vector<float> &texCoords, tcu::Vector<float, N> (&dst)[4])
{
    DE_ASSERT((int)texCoords.size() == 4 * N);
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < N; j++)
            dst[i][j] = texCoords[i * N + j];
}

#if defined(DE_DEBUG)
// Whether offsets correspond to the sample offsets used with plain textureGather().
static inline bool isZeroOffsetOffsets(const IVec2 (&offsets)[4])
{
    IVec2 ref[4];
    SinglePixelOffsets(IVec2(0))(IVec2(), ref);
    return std::equal(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), DE_ARRAY_BEGIN(ref));
}
#endif

template <typename ColorScalarType>
static tcu::Vector<ColorScalarType, 4> gatherOffsets(const tcu::Texture2DView &texture, const tcu::Sampler &sampler,
                                                     const Vec2 &coord, int componentNdx, const IVec2 (&offsets)[4])
{
    return texture.gatherOffsets(sampler, coord.x(), coord.y(), componentNdx, offsets).cast<ColorScalarType>();
}

template <typename ColorScalarType>
static tcu::Vector<ColorScalarType, 4> gatherOffsets(const tcu::Texture2DArrayView &texture,
                                                     const tcu::Sampler &sampler, const Vec3 &coord, int componentNdx,
                                                     const IVec2 (&offsets)[4])
{
    return texture.gatherOffsets(sampler, coord.x(), coord.y(), coord.z(), componentNdx, offsets)
        .cast<ColorScalarType>();
}

template <typename ColorScalarType>
static tcu::Vector<ColorScalarType, 4> gatherOffsets(const tcu::TextureCubeView &texture, const tcu::Sampler &sampler,
                                                     const Vec3 &coord, int componentNdx, const IVec2 (&offsets)[4])
{
    DE_ASSERT(isZeroOffsetOffsets(offsets));
    DE_UNREF(offsets);
    return texture.gather(sampler, coord.x(), coord.y(), coord.z(), componentNdx).cast<ColorScalarType>();
}

static Vec4 gatherOffsetsCompare(const tcu::Texture2DView &texture, const tcu::Sampler &sampler, float refZ,
                                 const Vec2 &coord, const IVec2 (&offsets)[4])
{
    return texture.gatherOffsetsCompare(sampler, refZ, coord.x(), coord.y(), offsets);
}

static Vec4 gatherOffsetsCompare(const tcu::Texture2DArrayView &texture, const tcu::Sampler &sampler, float refZ,
                                 const Vec3 &coord, const IVec2 (&offsets)[4])
{
    return texture.gatherOffsetsCompare(sampler, refZ, coord.x(), coord.y(), coord.z(), offsets);
}

static Vec4 gatherOffsetsCompare(const tcu::TextureCubeView &texture, const tcu::Sampler &sampler, float refZ,
                                 const Vec3 &coord, const IVec2 (&offsets)[4])
{
    DE_ASSERT(isZeroOffsetOffsets(offsets));
    DE_UNREF(offsets);
    return texture.gatherCompare(sampler, refZ, coord.x(), coord.y(), coord.z());
}

template <typename PrecType, typename ColorScalarT>
static bool isGatherOffsetsResultValid(const tcu::TextureCubeView &texture, const tcu::Sampler &sampler,
                                       const PrecType &prec, const Vec3 &coord, int componentNdx,
                                       const IVec2 (&offsets)[4], const tcu::Vector<ColorScalarT, 4> &result)
{
    DE_ASSERT(isZeroOffsetOffsets(offsets));
    DE_UNREF(offsets);
    return tcu::isGatherResultValid(texture, sampler, prec, coord, componentNdx, result);
}

static bool isGatherOffsetsCompareResultValid(const tcu::TextureCubeView &texture, const tcu::Sampler &sampler,
                                              const tcu::TexComparePrecision &prec, const Vec3 &coord,
                                              const IVec2 (&offsets)[4], float cmpReference, const Vec4 &result)
{
    DE_ASSERT(isZeroOffsetOffsets(offsets));
    DE_UNREF(offsets);
    return tcu::isGatherCompareResultValid(texture, sampler, prec, coord, cmpReference, result);
}

template <typename ColorScalarType, typename PrecType, typename TexViewT, typename TexCoordT>
static bool verifyGatherOffsets(TestLog &log, const ConstPixelBufferAccess &result, const TexViewT &texture,
                                const TexCoordT (&texCoords)[4], const tcu::Sampler &sampler,
                                const PrecType &lookupPrec, int componentNdx, const PixelOffsets &getPixelOffsets)
{
    typedef tcu::Vector<ColorScalarType, 4> ColorVec;

    const int width  = result.getWidth();
    const int height = result.getWidth();
    tcu::TextureLevel ideal(result.getFormat(), width, height);
    const PixelBufferAccess idealAccess = ideal.getAccess();
    tcu::Surface errorMask(width, height);
    bool success = true;

    tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec());

    for (int py = 0; py < height; py++)
        for (int px = 0; px < width; px++)
        {
            IVec2 offsets[4];
            getPixelOffsets(IVec2(px, py), offsets);

            const Vec2 viewportCoord = (Vec2((float)px, (float)py) + 0.5f) / Vec2((float)width, (float)height);
            const TexCoordT texCoord = triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
            const ColorVec resultPix = result.getPixelT<ColorScalarType>(px, py);
            const ColorVec idealPix = gatherOffsets<ColorScalarType>(texture, sampler, texCoord, componentNdx, offsets);

            idealAccess.setPixel(idealPix, px, py);

            if (tcu::boolAny(
                    tcu::logicalAnd(lookupPrec.colorMask,
                                    tcu::greaterThan(tcu::absDiff(resultPix, idealPix),
                                                     lookupPrec.colorThreshold.template cast<ColorScalarType>()))))
            {
                if (!isGatherOffsetsResultValid(texture, sampler, lookupPrec, texCoord, componentNdx, offsets,
                                                resultPix))
                {
                    errorMask.setPixel(px, py, tcu::RGBA::red());
                    success = false;
                }
            }
        }

    log << TestLog::ImageSet("VerifyResult", "Verification result")
        << TestLog::Image("Rendered", "Rendered image", result);

    if (!success)
    {
        log << TestLog::Image("Reference", "Ideal reference image", ideal)
            << TestLog::Image("ErrorMask", "Error mask", errorMask);
    }

    log << TestLog::EndImageSet;

    return success;
}

class PixelCompareRefZ
{
public:
    virtual float operator()(const IVec2 &pixCoord) const = 0;
};

class PixelCompareRefZDefault : public PixelCompareRefZ
{
public:
    PixelCompareRefZDefault(const IVec2 &renderSize) : m_renderSize(renderSize)
    {
    }

    float operator()(const IVec2 &pixCoord) const
    {
        return ((float)pixCoord.x() + 0.5f) / (float)m_renderSize.x();
    }

private:
    IVec2 m_renderSize;
};

template <typename TexViewT, typename TexCoordT>
static bool verifyGatherOffsetsCompare(TestLog &log, const ConstPixelBufferAccess &result, const TexViewT &texture,
                                       const TexCoordT (&texCoords)[4], const tcu::Sampler &sampler,
                                       const tcu::TexComparePrecision &compPrec, const PixelCompareRefZ &getPixelRefZ,
                                       const PixelOffsets &getPixelOffsets)
{
    const int width  = result.getWidth();
    const int height = result.getWidth();
    tcu::Surface ideal(width, height);
    const PixelBufferAccess idealAccess = ideal.getAccess();
    tcu::Surface errorMask(width, height);
    bool success = true;

    tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec());

    for (int py = 0; py < height; py++)
        for (int px = 0; px < width; px++)
        {
            IVec2 offsets[4];
            getPixelOffsets(IVec2(px, py), offsets);

            const Vec2 viewportCoord = (Vec2((float)px, (float)py) + 0.5f) / Vec2((float)width, (float)height);
            const TexCoordT texCoord = triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
            const float refZ         = getPixelRefZ(IVec2(px, py));
            const Vec4 resultPix     = result.getPixel(px, py);
            const Vec4 idealPix      = gatherOffsetsCompare(texture, sampler, refZ, texCoord, offsets);

            idealAccess.setPixel(idealPix, px, py);

            if (!tcu::boolAll(tcu::equal(resultPix, idealPix)))
            {
                if (!isGatherOffsetsCompareResultValid(texture, sampler, compPrec, texCoord, offsets, refZ, resultPix))
                {
                    errorMask.setPixel(px, py, tcu::RGBA::red());
                    success = false;
                }
            }
        }

    log << TestLog::ImageSet("VerifyResult", "Verification result")
        << TestLog::Image("Rendered", "Rendered image", result);

    if (!success)
    {
        log << TestLog::Image("Reference", "Ideal reference image", ideal)
            << TestLog::Image("ErrorMask", "Error mask", errorMask);
    }

    log << TestLog::EndImageSet;

    return success;
}

static bool verifySingleColored(TestLog &log, const ConstPixelBufferAccess &result, const Vec4 &refColor)
{
    const int width  = result.getWidth();
    const int height = result.getWidth();
    tcu::Surface ideal(width, height);
    const PixelBufferAccess idealAccess = ideal.getAccess();
    tcu::Surface errorMask(width, height);
    bool success = true;

    tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec());
    tcu::clear(idealAccess, refColor);

    for (int py = 0; py < height; py++)
        for (int px = 0; px < width; px++)
        {
            if (result.getPixel(px, py) != refColor)
            {
                errorMask.setPixel(px, py, tcu::RGBA::red());
                success = false;
            }
        }

    log << TestLog::ImageSet("VerifyResult", "Verification result")
        << TestLog::Image("Rendered", "Rendered image", result);

    if (!success)
    {
        log << TestLog::Image("Reference", "Ideal reference image", ideal)
            << TestLog::Image("ErrorMask", "Error mask", errorMask);
    }

    log << TestLog::EndImageSet;

    return success;
}

enum GatherType
{
    GATHERTYPE_BASIC = 0,
    GATHERTYPE_OFFSET,
    GATHERTYPE_OFFSET_DYNAMIC,
    GATHERTYPE_OFFSETS,

    GATHERTYPE_LAST
};

enum GatherCaseFlags
{
    GATHERCASE_MIPMAP_INCOMPLETE        = (1 << 0), //!< Excercise special case of sampling mipmap-incomplete texture
    GATHERCASE_DONT_SAMPLE_CUBE_CORNERS = (1 << 1)  //!< For cube map cases: do not sample cube corners
};

static inline const char *gatherTypeName(GatherType type)
{
    switch (type)
    {
    case GATHERTYPE_BASIC:
        return "basic";
    case GATHERTYPE_OFFSET:
        return "offset";
    case GATHERTYPE_OFFSET_DYNAMIC:
        return "offset_dynamic";
    case GATHERTYPE_OFFSETS:
        return "offsets";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static inline const char *gatherTypeDescription(GatherType type)
{
    switch (type)
    {
    case GATHERTYPE_BASIC:
        return "textureGather";
    case GATHERTYPE_OFFSET:
        return "textureGatherOffset";
    case GATHERTYPE_OFFSET_DYNAMIC:
        return "textureGatherOffset with dynamic offsets";
    case GATHERTYPE_OFFSETS:
        return "textureGatherOffsets";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static inline bool requireGpuShader5(GatherType gatherType)
{
    return gatherType == GATHERTYPE_OFFSET_DYNAMIC || gatherType == GATHERTYPE_OFFSETS;
}

struct GatherArgs
{
    int componentNdx; // If negative, implicit component index 0 is used (i.e. the parameter is not given).
    IVec2 offsets
        [4]; // \note Unless GATHERTYPE_OFFSETS is used, only offsets[0] is relevant; also, for GATHERTYPE_OFFSET_DYNAMIC, none are relevant.

    GatherArgs(void) : componentNdx(-1)
    {
        std::fill(DE_ARRAY_BEGIN(offsets), DE_ARRAY_END(offsets), IVec2());
    }

    GatherArgs(int comp, const IVec2 &off0 = IVec2(), const IVec2 &off1 = IVec2(), const IVec2 &off2 = IVec2(),
               const IVec2 &off3 = IVec2())
        : componentNdx(comp)
    {
        offsets[0] = off0;
        offsets[1] = off1;
        offsets[2] = off2;
        offsets[3] = off3;
    }
};

static MovePtr<PixelOffsets> makePixelOffsetsFunctor(GatherType gatherType, const GatherArgs &gatherArgs,
                                                     const IVec2 &offsetRange)
{
    if (gatherType == GATHERTYPE_BASIC || gatherType == GATHERTYPE_OFFSET)
    {
        const IVec2 offset = gatherType == GATHERTYPE_BASIC ? IVec2(0) : gatherArgs.offsets[0];
        return MovePtr<PixelOffsets>(new SinglePixelOffsets(offset));
    }
    else if (gatherType == GATHERTYPE_OFFSET_DYNAMIC)
    {
        return MovePtr<PixelOffsets>(new DynamicSinglePixelOffsets(offsetRange));
    }
    else if (gatherType == GATHERTYPE_OFFSETS)
        return MovePtr<PixelOffsets>(new MultiplePixelOffsets(gatherArgs.offsets[0], gatherArgs.offsets[1],
                                                              gatherArgs.offsets[2], gatherArgs.offsets[3]));
    else
    {
        DE_ASSERT(false);
        return MovePtr<PixelOffsets>(DE_NULL);
    }
}

static inline glu::DataType getSamplerType(TextureType textureType, const tcu::TextureFormat &format)
{
    if (isDepthFormat(format))
    {
        switch (textureType)
        {
        case TEXTURETYPE_2D:
            return glu::TYPE_SAMPLER_2D_SHADOW;
        case TEXTURETYPE_2D_ARRAY:
            return glu::TYPE_SAMPLER_2D_ARRAY_SHADOW;
        case TEXTURETYPE_CUBE:
            return glu::TYPE_SAMPLER_CUBE_SHADOW;
        default:
            DE_ASSERT(false);
            return glu::TYPE_LAST;
        }
    }
    else
    {
        switch (textureType)
        {
        case TEXTURETYPE_2D:
            return glu::getSampler2DType(format);
        case TEXTURETYPE_2D_ARRAY:
            return glu::getSampler2DArrayType(format);
        case TEXTURETYPE_CUBE:
            return glu::getSamplerCubeType(format);
        default:
            DE_ASSERT(false);
            return glu::TYPE_LAST;
        }
    }
}

static inline glu::DataType getSamplerGatherResultType(glu::DataType samplerType)
{
    switch (samplerType)
    {
    case glu::TYPE_SAMPLER_2D_SHADOW:
    case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW:
    case glu::TYPE_SAMPLER_CUBE_SHADOW:
    case glu::TYPE_SAMPLER_2D:
    case glu::TYPE_SAMPLER_2D_ARRAY:
    case glu::TYPE_SAMPLER_CUBE:
        return glu::TYPE_FLOAT_VEC4;

    case glu::TYPE_INT_SAMPLER_2D:
    case glu::TYPE_INT_SAMPLER_2D_ARRAY:
    case glu::TYPE_INT_SAMPLER_CUBE:
        return glu::TYPE_INT_VEC4;

    case glu::TYPE_UINT_SAMPLER_2D:
    case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
    case glu::TYPE_UINT_SAMPLER_CUBE:
        return glu::TYPE_UINT_VEC4;

    default:
        DE_ASSERT(false);
        return glu::TYPE_LAST;
    }
}

static inline int getNumTextureSamplingDimensions(TextureType type)
{
    switch (type)
    {
    case TEXTURETYPE_2D:
        return 2;
    case TEXTURETYPE_2D_ARRAY:
        return 3;
    case TEXTURETYPE_CUBE:
        return 3;
    default:
        DE_ASSERT(false);
        return -1;
    }
}

static uint32_t getGLTextureType(TextureType type)
{
    switch (type)
    {
    case TEXTURETYPE_2D:
        return GL_TEXTURE_2D;
    case TEXTURETYPE_2D_ARRAY:
        return GL_TEXTURE_2D_ARRAY;
    case TEXTURETYPE_CUBE:
        return GL_TEXTURE_CUBE_MAP;
    default:
        DE_ASSERT(false);
        return (uint32_t)-1;
    }
}

enum OffsetSize
{
    OFFSETSIZE_NONE = 0,
    OFFSETSIZE_MINIMUM_REQUIRED,
    OFFSETSIZE_IMPLEMENTATION_MAXIMUM,

    OFFSETSIZE_LAST
};

static inline bool isMipmapFilter(tcu::Sampler::FilterMode filter)
{
    switch (filter)
    {
    case tcu::Sampler::NEAREST:
    case tcu::Sampler::LINEAR:
        return false;

    case tcu::Sampler::NEAREST_MIPMAP_NEAREST:
    case tcu::Sampler::NEAREST_MIPMAP_LINEAR:
    case tcu::Sampler::LINEAR_MIPMAP_NEAREST:
    case tcu::Sampler::LINEAR_MIPMAP_LINEAR:
        return true;

    default:
        DE_ASSERT(false);
        return false;
    }
}

class TextureGatherCase : public TestCase
{
public:
    TextureGatherCase(Context &context, const char *name, const char *description, TextureType textureType,
                      GatherType gatherType, OffsetSize offsetSize, tcu::TextureFormat textureFormat,
                      tcu::Sampler::CompareMode
                          shadowCompareMode, //!< Should be COMPAREMODE_NONE iff textureFormat is a depth format.
                      tcu::Sampler::WrapMode wrapS, tcu::Sampler::WrapMode wrapT, const MaybeTextureSwizzle &texSwizzle,
                      // \note Filter modes have no effect on gather (except when it comes to
                      //          texture completeness); these are supposed to test just that.
                      tcu::Sampler::FilterMode minFilter, tcu::Sampler::FilterMode magFilter, int baseLevel,
                      uint32_t flags);

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

protected:
    IVec2 getOffsetRange(void) const;

    template <typename TexViewT, typename TexCoordT>
    bool verify(const ConstPixelBufferAccess &rendered, const TexViewT &texture, const TexCoordT (&bottomLeft)[4],
                const GatherArgs &gatherArgs) const;

    virtual void generateIterations(void)                                               = 0;
    virtual void createAndUploadTexture(void)                                           = 0;
    virtual int getNumIterations(void) const                                            = 0;
    virtual GatherArgs getGatherArgs(int iterationNdx) const                            = 0;
    virtual vector<float> computeQuadTexCoord(int iterationNdx) const                   = 0;
    virtual bool verify(int iterationNdx, const ConstPixelBufferAccess &rendered) const = 0;

    const GatherType m_gatherType;
    const OffsetSize m_offsetSize;
    const tcu::TextureFormat m_textureFormat;
    const tcu::Sampler::CompareMode m_shadowCompareMode;
    const tcu::Sampler::WrapMode m_wrapS;
    const tcu::Sampler::WrapMode m_wrapT;
    const MaybeTextureSwizzle m_textureSwizzle;
    const tcu::Sampler::FilterMode m_minFilter;
    const tcu::Sampler::FilterMode m_magFilter;
    const int m_baseLevel;
    const uint32_t m_flags;

private:
    enum
    {
        SPEC_MAX_MIN_OFFSET = -8,
        SPEC_MIN_MAX_OFFSET = 7
    };

    static const IVec2 RENDER_SIZE;

    glu::VertexSource genVertexShaderSource(bool requireGpuShader5, int numTexCoordComponents,
                                            bool useNormalizedCoordInput);
    glu::FragmentSource genFragmentShaderSource(bool requireGpuShader5, int numTexCoordComponents,
                                                glu::DataType samplerType, const string &funcCall,
                                                bool useNormalizedCoordInput, bool usePixCoord);
    string genGatherFuncCall(GatherType, const tcu::TextureFormat &, const GatherArgs &, const string &refZExpr,
                             const IVec2 &offsetRange, int indentationDepth);
    glu::ProgramSources genProgramSources(GatherType, TextureType, const tcu::TextureFormat &, const GatherArgs &,
                                          const string &refZExpr, const IVec2 &offsetRange);

    const TextureType m_textureType;

    const tcu::TextureFormat m_colorBufferFormat;
    MovePtr<glu::Renderbuffer> m_colorBuffer;
    MovePtr<glu::Framebuffer> m_fbo;

    int m_currentIteration;
    MovePtr<ShaderProgram> m_program;
};

const IVec2 TextureGatherCase::RENDER_SIZE = IVec2(64, 64);

TextureGatherCase::TextureGatherCase(
    Context &context, const char *name, const char *description, TextureType textureType, GatherType gatherType,
    OffsetSize offsetSize, tcu::TextureFormat textureFormat,
    tcu::Sampler::CompareMode shadowCompareMode, //!< Should be COMPAREMODE_NONE iff textureType == TEXTURETYPE_NORMAL.
    tcu::Sampler::WrapMode wrapS, tcu::Sampler::WrapMode wrapT, const MaybeTextureSwizzle &textureSwizzle,
    tcu::Sampler::FilterMode minFilter, tcu::Sampler::FilterMode magFilter, int baseLevel, uint32_t flags)
    : TestCase(context, name, description)
    , m_gatherType(gatherType)
    , m_offsetSize(offsetSize)
    , m_textureFormat(textureFormat)
    , m_shadowCompareMode(shadowCompareMode)
    , m_wrapS(wrapS)
    , m_wrapT(wrapT)
    , m_textureSwizzle(textureSwizzle)
    , m_minFilter(minFilter)
    , m_magFilter(magFilter)
    , m_baseLevel(baseLevel)
    , m_flags(flags)
    , m_textureType(textureType)
    , m_colorBufferFormat(tcu::TextureFormat(
          tcu::TextureFormat::RGBA, isDepthFormat(textureFormat) ? tcu::TextureFormat::UNORM_INT8 : textureFormat.type))
    , m_currentIteration(0)
{
    DE_ASSERT((m_gatherType == GATHERTYPE_BASIC) == (m_offsetSize == OFFSETSIZE_NONE));
    DE_ASSERT((m_shadowCompareMode != tcu::Sampler::COMPAREMODE_NONE) == isDepthFormat(m_textureFormat));
    DE_ASSERT(isUnormFormatType(m_colorBufferFormat.type) ||
              m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT8 ||
              m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT16 ||
              m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT8 ||
              m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT16);
    DE_ASSERT(glu::isGLInternalColorFormatFilterable(glu::getInternalFormat(m_colorBufferFormat)) ||
              (m_magFilter == tcu::Sampler::NEAREST &&
               (m_minFilter == tcu::Sampler::NEAREST || m_minFilter == tcu::Sampler::NEAREST_MIPMAP_NEAREST)));
    DE_ASSERT(isMipmapFilter(m_minFilter) || !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE));
    DE_ASSERT(m_textureType == TEXTURETYPE_CUBE || !(m_flags & GATHERCASE_DONT_SAMPLE_CUBE_CORNERS));
    DE_ASSERT(!((m_flags & GATHERCASE_MIPMAP_INCOMPLETE) &&
                isDepthFormat(m_textureFormat))); // It's not clear what shadow textures should return when incomplete.
}

IVec2 TextureGatherCase::getOffsetRange(void) const
{
    switch (m_offsetSize)
    {
    case OFFSETSIZE_NONE:
        return IVec2(0);

    case OFFSETSIZE_MINIMUM_REQUIRED:
        // \note Defined by spec.
        return IVec2(SPEC_MAX_MIN_OFFSET, SPEC_MIN_MAX_OFFSET);

    case OFFSETSIZE_IMPLEMENTATION_MAXIMUM:
        return IVec2(m_context.getContextInfo().getInt(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET),
                     m_context.getContextInfo().getInt(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET));

    default:
        DE_ASSERT(false);
        return IVec2(-1);
    }
}

glu::VertexSource TextureGatherCase::genVertexShaderSource(bool requireGpuShader5, int numTexCoordComponents,
                                                           bool useNormalizedCoordInput)
{
    DE_ASSERT(numTexCoordComponents == 2 || numTexCoordComponents == 3);
    const string texCoordType = "vec" + de::toString(numTexCoordComponents);
    std::string vertexSource  = "${GLSL_VERSION_DECL}\n" + string(requireGpuShader5 ? "${GPU_SHADER5_REQUIRE}\n" : "") +
                               "\n"
                               "in highp vec2 a_position;\n"
                               "in highp " +
                               texCoordType + " a_texCoord;\n" +
                               (useNormalizedCoordInput ? "in highp vec2 a_normalizedCoord; // (0,0) to (1,1)\n" : "") +
                               "\n"
                               "out highp " +
                               texCoordType + " v_texCoord;\n" +
                               (useNormalizedCoordInput ? "out highp vec2 v_normalizedCoord;\n" : "") +
                               "\n"
                               "void main (void)\n"
                               "{\n"
                               "    gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);\n"
                               "    v_texCoord = a_texCoord;\n" +
                               (useNormalizedCoordInput ? "\tv_normalizedCoord = a_normalizedCoord;\n" : "") + "}\n";
    return glu::VertexSource(specializeShader(m_context, vertexSource.c_str()));
}

glu::FragmentSource TextureGatherCase::genFragmentShaderSource(bool requireGpuShader5, int numTexCoordComponents,
                                                               glu::DataType samplerType, const string &funcCall,
                                                               bool useNormalizedCoordInput, bool usePixCoord)
{
    DE_ASSERT(glu::isDataTypeSampler(samplerType));
    DE_ASSERT(de::inRange(numTexCoordComponents, 2, 3));
    DE_ASSERT(!usePixCoord || useNormalizedCoordInput);

    const string texCoordType = "vec" + de::toString(numTexCoordComponents);

    std::string fragmentSource =
        "${GLSL_VERSION_DECL}\n" + string(requireGpuShader5 ? "${GPU_SHADER5_REQUIRE}\n" : "") +
        "\n"
        "layout (location = 0) out mediump " +
        glu::getDataTypeName(getSamplerGatherResultType(samplerType)) +
        " o_color;\n"
        "\n"
        "in highp " +
        texCoordType + " v_texCoord;\n" + (useNormalizedCoordInput ? "in highp vec2 v_normalizedCoord;\n" : "") +
        "\n"
        "uniform highp " +
        string(glu::getDataTypeName(samplerType)) + " u_sampler;\n" +
        (useNormalizedCoordInput ? "uniform highp vec2 u_viewportSize;\n" : "") +
        "\n"
        "void main(void)\n"
        "{\n" +
        (usePixCoord ? "\tivec2 pixCoord = ivec2(v_normalizedCoord*u_viewportSize);\n" : "") +
        "    o_color = " + funcCall +
        ";\n"
        "}\n";

    return glu::FragmentSource(specializeShader(m_context, fragmentSource.c_str()));
}

string TextureGatherCase::genGatherFuncCall(GatherType gatherType, const tcu::TextureFormat &textureFormat,
                                            const GatherArgs &gatherArgs, const string &refZExpr,
                                            const IVec2 &offsetRange, int indentationDepth)
{
    string result;

    switch (gatherType)
    {
    case GATHERTYPE_BASIC:
        result += "textureGather";
        break;
    case GATHERTYPE_OFFSET: // \note Fallthrough.
    case GATHERTYPE_OFFSET_DYNAMIC:
        result += "textureGatherOffset";
        break;
    case GATHERTYPE_OFFSETS:
        result += "textureGatherOffsets";
        break;
    default:
        DE_ASSERT(false);
    }

    result += "(u_sampler, v_texCoord";

    if (isDepthFormat(textureFormat))
    {
        DE_ASSERT(gatherArgs.componentNdx < 0);
        result += ", " + refZExpr;
    }

    if (gatherType == GATHERTYPE_OFFSET || gatherType == GATHERTYPE_OFFSET_DYNAMIC || gatherType == GATHERTYPE_OFFSETS)
    {
        result += ", ";
        switch (gatherType)
        {
        case GATHERTYPE_OFFSET:
            result += "ivec2" + de::toString(gatherArgs.offsets[0]);
            break;

        case GATHERTYPE_OFFSET_DYNAMIC:
            result += "pixCoord.yx % ivec2(" + de::toString(offsetRange.y() - offsetRange.x() + 1) + ") + " +
                      de::toString(offsetRange.x());
            break;

        case GATHERTYPE_OFFSETS:
            result += "ivec2[4](\n" + string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[0]) +
                      ",\n" + string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[1]) + ",\n" +
                      string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[2]) + ",\n" +
                      string(indentationDepth, '\t') + "\tivec2" + de::toString(gatherArgs.offsets[3]) + ")\n" +
                      string(indentationDepth, '\t') + "\t";
            break;

        default:
            DE_ASSERT(false);
        }
    }

    if (gatherArgs.componentNdx >= 0)
    {
        DE_ASSERT(gatherArgs.componentNdx < 4);
        result += ", " + de::toString(gatherArgs.componentNdx);
    }

    result += ")";

    return result;
}

// \note If componentNdx for genProgramSources() is -1, component index is not specified.
glu::ProgramSources TextureGatherCase::genProgramSources(GatherType gatherType, TextureType textureType,
                                                         const tcu::TextureFormat &textureFormat,
                                                         const GatherArgs &gatherArgs, const string &refZExpr,
                                                         const IVec2 &offsetRange)
{
    const bool usePixCoord          = gatherType == GATHERTYPE_OFFSET_DYNAMIC;
    const bool useNormalizedCoord   = usePixCoord || isDepthFormat(textureFormat);
    const bool isDynamicOffset      = gatherType == GATHERTYPE_OFFSET_DYNAMIC;
    const bool isShadow             = isDepthFormat(textureFormat);
    const glu::DataType samplerType = getSamplerType(textureType, textureFormat);
    const int numDims               = getNumTextureSamplingDimensions(textureType);
    const string funcCall = genGatherFuncCall(gatherType, textureFormat, gatherArgs, refZExpr, offsetRange, 1);

    return glu::ProgramSources() << genVertexShaderSource(requireGpuShader5(gatherType), numDims,
                                                          isDynamicOffset || isShadow)
                                 << genFragmentShaderSource(requireGpuShader5(gatherType), numDims, samplerType,
                                                            funcCall, useNormalizedCoord, usePixCoord);
}

void TextureGatherCase::init(void)
{
    TestLog &log                        = m_testCtx.getLog();
    const glu::RenderContext &renderCtx = m_context.getRenderContext();
    const glw::Functions &gl            = renderCtx.getFunctions();
    const uint32_t texTypeGL            = getGLTextureType(m_textureType);
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    // Check prerequisites.
    if (requireGpuShader5(m_gatherType) && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"))
        throw tcu::NotSupportedError("GL_EXT_gpu_shader5 required");

    // Log and check implementation offset limits, if appropriate.
    if (m_offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM)
    {
        const IVec2 offsetRange = getOffsetRange();
        log << TestLog::Integer("ImplementationMinTextureGatherOffset",
                                "Implementation's value for GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET", "", QP_KEY_TAG_NONE,
                                offsetRange[0])
            << TestLog::Integer("ImplementationMaxTextureGatherOffset",
                                "Implementation's value for GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET", "", QP_KEY_TAG_NONE,
                                offsetRange[1]);
        TCU_CHECK_MSG(
            offsetRange[0] <= SPEC_MAX_MIN_OFFSET,
            ("GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET must be at most " + de::toString((int)SPEC_MAX_MIN_OFFSET)).c_str());
        TCU_CHECK_MSG(offsetRange[1] >= SPEC_MIN_MAX_OFFSET, ("GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET must be at least " +
                                                              de::toString((int)SPEC_MIN_MAX_OFFSET))
                                                                 .c_str());
    }

    if (!isContextTypeES(m_context.getRenderContext().getType()))
        gl.enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);

    // Create rbo and fbo.

    m_colorBuffer = MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(renderCtx));
    gl.bindRenderbuffer(GL_RENDERBUFFER, **m_colorBuffer);
    gl.renderbufferStorage(GL_RENDERBUFFER, glu::getInternalFormat(m_colorBufferFormat), RENDER_SIZE.x(),
                           RENDER_SIZE.y());
    GLU_EXPECT_NO_ERROR(gl.getError(), "Create and setup renderbuffer object");

    m_fbo = MovePtr<glu::Framebuffer>(new glu::Framebuffer(renderCtx));
    gl.bindFramebuffer(GL_FRAMEBUFFER, **m_fbo);
    gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **m_colorBuffer);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Create and setup framebuffer object");

    log << TestLog::Message << "Using a framebuffer object with renderbuffer with format "
        << glu::getTextureFormatName(glu::getInternalFormat(m_colorBufferFormat)) << " and size " << RENDER_SIZE
        << TestLog::EndMessage;

    // Generate subclass-specific iterations.

    generateIterations();
    m_currentIteration = 0;

    // Initialize texture.

    createAndUploadTexture();
    gl.texParameteri(texTypeGL, GL_TEXTURE_WRAP_S, glu::getGLWrapMode(m_wrapS));
    gl.texParameteri(texTypeGL, GL_TEXTURE_WRAP_T, glu::getGLWrapMode(m_wrapT));
    gl.texParameteri(texTypeGL, GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(m_minFilter));
    gl.texParameteri(texTypeGL, GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(m_magFilter));

    if (m_baseLevel != 0)
        gl.texParameteri(texTypeGL, GL_TEXTURE_BASE_LEVEL, m_baseLevel);

    if (isDepthFormat(m_textureFormat))
    {
        gl.texParameteri(texTypeGL, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
        gl.texParameteri(texTypeGL, GL_TEXTURE_COMPARE_FUNC, glu::getGLCompareFunc(m_shadowCompareMode));
    }

    if (m_textureSwizzle.isSome())
    {
        const uint32_t swizzleNamesGL[4] = {GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, GL_TEXTURE_SWIZZLE_B,
                                            GL_TEXTURE_SWIZZLE_A};

        for (int i = 0; i < 4; i++)
        {
            const uint32_t curGLSwizzle = getGLTextureSwizzleComponent(m_textureSwizzle.getSwizzle()[i]);
            gl.texParameteri(texTypeGL, swizzleNamesGL[i], curGLSwizzle);
        }
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "Set texture parameters");

    log << TestLog::Message << "Texture base level is " << m_baseLevel << TestLog::EndMessage << TestLog::Message
        << "s and t wrap modes are " << glu::getTextureWrapModeName(glu::getGLWrapMode(m_wrapS)) << " and "
        << glu::getTextureWrapModeName(glu::getGLWrapMode(m_wrapT)) << ", respectively" << TestLog::EndMessage
        << TestLog::Message << "Minification and magnification filter modes are "
        << glu::getTextureFilterName(glu::getGLFilterMode(m_minFilter)) << " and "
        << glu::getTextureFilterName(glu::getGLFilterMode(m_magFilter)) << ", respectively "
        << ((m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ? "(note that they cause the texture to be incomplete)" :
                                                       "(note that they should have no effect on gather result)")
        << TestLog::EndMessage << TestLog::Message << "Using texture swizzle " << m_textureSwizzle
        << TestLog::EndMessage;

    if (m_shadowCompareMode != tcu::Sampler::COMPAREMODE_NONE)
        log << TestLog::Message << "Using texture compare func "
            << glu::getCompareFuncName(glu::getGLCompareFunc(m_shadowCompareMode)) << TestLog::EndMessage;
}

void TextureGatherCase::deinit(void)
{
    if (!isContextTypeES(m_context.getRenderContext().getType()))
        m_context.getRenderContext().getFunctions().disable(GL_TEXTURE_CUBE_MAP_SEAMLESS);

    m_program     = MovePtr<ShaderProgram>(DE_NULL);
    m_fbo         = MovePtr<glu::Framebuffer>(DE_NULL);
    m_colorBuffer = MovePtr<glu::Renderbuffer>(DE_NULL);
}

TextureGatherCase::IterateResult TextureGatherCase::iterate(void)
{
    TestLog &log = m_testCtx.getLog();
    const tcu::ScopedLogSection iterationSection(log, "Iteration" + de::toString(m_currentIteration),
                                                 "Iteration " + de::toString(m_currentIteration));
    const glu::RenderContext &renderCtx    = m_context.getRenderContext();
    const glw::Functions &gl               = renderCtx.getFunctions();
    const GatherArgs &gatherArgs           = getGatherArgs(m_currentIteration);
    const string refZExpr                  = "v_normalizedCoord.x";
    const bool needPixelCoordInShader      = m_gatherType == GATHERTYPE_OFFSET_DYNAMIC;
    const bool needNormalizedCoordInShader = needPixelCoordInShader || isDepthFormat(m_textureFormat);

    // Generate a program appropriate for this iteration.

    m_program = MovePtr<ShaderProgram>(
        new ShaderProgram(renderCtx, genProgramSources(m_gatherType, m_textureType, m_textureFormat, gatherArgs,
                                                       refZExpr, getOffsetRange())));
    if (m_currentIteration == 0)
        m_testCtx.getLog() << *m_program;
    else
        m_testCtx.getLog()
            << TestLog::Message
            << "Using a program similar to the previous one, except with a gather function call as follows:\n"
            << genGatherFuncCall(m_gatherType, m_textureFormat, gatherArgs, refZExpr, getOffsetRange(), 0)
            << TestLog::EndMessage;
    if (!m_program->isOk())
    {
        if (m_currentIteration != 0)
            m_testCtx.getLog() << *m_program;
        TCU_FAIL("Failed to build program");
    }

    // Render.

    gl.viewport(0, 0, RENDER_SIZE.x(), RENDER_SIZE.y());
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);

    {
        const float position[4 * 2] = {
            -1.0f, -1.0f, -1.0f, +1.0f, +1.0f, -1.0f, +1.0f, +1.0f,
        };

        const float normalizedCoord[4 * 2] = {
            0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
        };

        const vector<float> texCoord = computeQuadTexCoord(m_currentIteration);

        vector<glu::VertexArrayBinding> attrBindings;
        attrBindings.push_back(glu::va::Float("a_position", 2, 4, 0, &position[0]));
        attrBindings.push_back(glu::va::Float("a_texCoord", (int)texCoord.size() / 4, 4, 0, &texCoord[0]));
        if (needNormalizedCoordInShader)
            attrBindings.push_back(glu::va::Float("a_normalizedCoord", 2, 4, 0, &normalizedCoord[0]));

        const uint16_t indices[6] = {0, 1, 2, 2, 1, 3};

        gl.useProgram(m_program->getProgram());

        {
            const int samplerUniformLocation = gl.getUniformLocation(m_program->getProgram(), "u_sampler");
            TCU_CHECK(samplerUniformLocation >= 0);
            gl.uniform1i(samplerUniformLocation, 0);
        }

        if (needPixelCoordInShader)
        {
            const int viewportSizeUniformLocation = gl.getUniformLocation(m_program->getProgram(), "u_viewportSize");
            TCU_CHECK(viewportSizeUniformLocation >= 0);
            gl.uniform2f(viewportSizeUniformLocation, (float)RENDER_SIZE.x(), (float)RENDER_SIZE.y());
        }

        if (texCoord.size() == 2 * 4)
        {
            Vec2 texCoordVec[4];
            computeTexCoordVecs(texCoord, texCoordVec);
            log << TestLog::Message << "Texture coordinates run from " << texCoordVec[0] << " to " << texCoordVec[3]
                << TestLog::EndMessage;
        }
        else if (texCoord.size() == 3 * 4)
        {
            Vec3 texCoordVec[4];
            computeTexCoordVecs(texCoord, texCoordVec);
            log << TestLog::Message << "Texture coordinates run from " << texCoordVec[0] << " to " << texCoordVec[3]
                << TestLog::EndMessage;
        }
        else
            DE_ASSERT(false);

        glu::draw(renderCtx, m_program->getProgram(), (int)attrBindings.size(), &attrBindings[0],
                  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
    }

    // Verify result.

    {
        const tcu::TextureLevel rendered = getPixels(renderCtx, RENDER_SIZE, m_colorBufferFormat);

        if (!verify(m_currentIteration, rendered.getAccess()))
        {
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
            return STOP;
        }
    }

    m_currentIteration++;
    if (m_currentIteration == (int)getNumIterations())
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        return STOP;
    }
    else
        return CONTINUE;
}

template <typename TexViewT, typename TexCoordT>
bool TextureGatherCase::verify(const ConstPixelBufferAccess &rendered, const TexViewT &texture,
                               const TexCoordT (&texCoords)[4], const GatherArgs &gatherArgs) const
{
    TestLog &log = m_testCtx.getLog();

    if (m_flags & GATHERCASE_MIPMAP_INCOMPLETE)
    {
        const int componentNdx = de::max(0, gatherArgs.componentNdx);
        const Vec4 incompleteColor(0.0f, 0.0f, 0.0f, 1.0f);
        const Vec4 refColor(incompleteColor[componentNdx]);
        const bool isOk = verifySingleColored(log, rendered, refColor);

        if (!isOk)
            log << TestLog::Message << "Note: expected color " << refColor << " for all pixels; "
                << incompleteColor[componentNdx] << " is component at index " << componentNdx << " in the color "
                << incompleteColor << ", which is used for incomplete textures" << TestLog::EndMessage;

        return isOk;
    }
    else
    {
        DE_ASSERT(m_colorBufferFormat.order == tcu::TextureFormat::RGBA);
        DE_ASSERT(m_colorBufferFormat.type == tcu::TextureFormat::UNORM_INT8 ||
                  m_colorBufferFormat.type == tcu::TextureFormat::UNSIGNED_INT8 ||
                  m_colorBufferFormat.type == tcu::TextureFormat::SIGNED_INT8);

        const MovePtr<PixelOffsets> pixelOffsets = makePixelOffsetsFunctor(m_gatherType, gatherArgs, getOffsetRange());
        const tcu::PixelFormat pixelFormat       = tcu::PixelFormat(8, 8, 8, 8);
        const IVec4 colorBits = tcu::max(glu::TextureTestUtil::getBitsVec(pixelFormat) - 1, tcu::IVec4(0));
        const IVec3 coordBits = m_textureType == TEXTURETYPE_2D       ? IVec3(20, 20, 0) :
                                m_textureType == TEXTURETYPE_CUBE     ? IVec3(10, 10, 10) :
                                m_textureType == TEXTURETYPE_2D_ARRAY ? IVec3(20, 20, 20) :
                                                                        IVec3(-1);
        const IVec3 uvwBits   = m_textureType == TEXTURETYPE_2D       ? IVec3(7, 7, 0) :
                                m_textureType == TEXTURETYPE_CUBE     ? IVec3(6, 6, 0) :
                                m_textureType == TEXTURETYPE_2D_ARRAY ? IVec3(7, 7, 7) :
                                                                        IVec3(-1);
        tcu::Sampler sampler;
        sampler.wrapS   = m_wrapS;
        sampler.wrapT   = m_wrapT;
        sampler.compare = m_shadowCompareMode;

        if (isDepthFormat(m_textureFormat))
        {
            tcu::TexComparePrecision comparePrec;
            comparePrec.coordBits     = coordBits;
            comparePrec.uvwBits       = uvwBits;
            comparePrec.referenceBits = 16;
            comparePrec.resultBits    = pixelFormat.redBits - 1;

            return verifyGatherOffsetsCompare(log, rendered, texture, texCoords, sampler, comparePrec,
                                              PixelCompareRefZDefault(RENDER_SIZE), *pixelOffsets);
        }
        else
        {
            const int componentNdx = de::max(0, gatherArgs.componentNdx);

            if (isUnormFormatType(m_textureFormat.type))
            {
                tcu::LookupPrecision lookupPrec;
                lookupPrec.colorThreshold = tcu::computeFixedPointThreshold(colorBits);
                lookupPrec.coordBits      = coordBits;
                lookupPrec.uvwBits        = uvwBits;
                lookupPrec.colorMask      = glu::TextureTestUtil::getCompareMask(pixelFormat);
                return verifyGatherOffsets<float>(log, rendered, texture, texCoords, sampler, lookupPrec, componentNdx,
                                                  *pixelOffsets);
            }
            else if (isUIntFormatType(m_textureFormat.type) || isSIntFormatType(m_textureFormat.type))
            {
                tcu::IntLookupPrecision lookupPrec;
                lookupPrec.colorThreshold = UVec4(0);
                lookupPrec.coordBits      = coordBits;
                lookupPrec.uvwBits        = uvwBits;
                lookupPrec.colorMask      = glu::TextureTestUtil::getCompareMask(pixelFormat);

                if (isUIntFormatType(m_textureFormat.type))
                    return verifyGatherOffsets<uint32_t>(log, rendered, texture, texCoords, sampler, lookupPrec,
                                                         componentNdx, *pixelOffsets);
                else if (isSIntFormatType(m_textureFormat.type))
                    return verifyGatherOffsets<int32_t>(log, rendered, texture, texCoords, sampler, lookupPrec,
                                                        componentNdx, *pixelOffsets);
                else
                {
                    DE_ASSERT(false);
                    return false;
                }
            }
            else
            {
                DE_ASSERT(false);
                return false;
            }
        }
    }
}

vector<GatherArgs> generateBasic2DCaseIterations(GatherType gatherType, const tcu::TextureFormat &textureFormat,
                                                 const IVec2 &offsetRange)
{
    const int numComponentCases =
        isDepthFormat(textureFormat) ?
            1 :
            4 + 1; // \note For non-depth textures, test explicit components 0 to 3 and implicit component 0.
    vector<GatherArgs> result;

    for (int componentCaseNdx = 0; componentCaseNdx < numComponentCases; componentCaseNdx++)
    {
        const int componentNdx = componentCaseNdx - 1;

        switch (gatherType)
        {
        case GATHERTYPE_BASIC:
            result.push_back(GatherArgs(componentNdx));
            break;

        case GATHERTYPE_OFFSET:
        {
            const int min  = offsetRange.x();
            const int max  = offsetRange.y();
            const int hmin = divRoundToZero(min, 2);
            const int hmax = divRoundToZero(max, 2);

            result.push_back(GatherArgs(componentNdx, IVec2(min, max)));

            if (componentCaseNdx ==
                0) // Don't test all offsets variants for all color components (they should be pretty orthogonal).
            {
                result.push_back(GatherArgs(componentNdx, IVec2(min, min)));
                result.push_back(GatherArgs(componentNdx, IVec2(max, min)));
                result.push_back(GatherArgs(componentNdx, IVec2(max, max)));

                result.push_back(GatherArgs(componentNdx, IVec2(0, hmax)));
                result.push_back(GatherArgs(componentNdx, IVec2(hmin, 0)));
                result.push_back(GatherArgs(componentNdx, IVec2(0, 0)));
            }

            break;
        }

        case GATHERTYPE_OFFSET_DYNAMIC:
            result.push_back(GatherArgs(componentNdx));
            break;

        case GATHERTYPE_OFFSETS:
        {
            const int min  = offsetRange.x();
            const int max  = offsetRange.y();
            const int hmin = divRoundToZero(min, 2);
            const int hmax = divRoundToZero(max, 2);

            result.push_back(
                GatherArgs(componentNdx, IVec2(min, min), IVec2(min, max), IVec2(max, min), IVec2(max, max)));

            if (componentCaseNdx ==
                0) // Don't test all offsets variants for all color components (they should be pretty orthogonal).
                result.push_back(
                    GatherArgs(componentNdx, IVec2(min, hmax), IVec2(hmin, max), IVec2(0, hmax), IVec2(hmax, 0)));
            break;
        }

        default:
            DE_ASSERT(false);
        }
    }

    return result;
}

class TextureGather2DCase : public TextureGatherCase
{
public:
    TextureGather2DCase(Context &context, const char *name, const char *description, GatherType gatherType,
                        OffsetSize offsetSize, tcu::TextureFormat textureFormat,
                        tcu::Sampler::CompareMode shadowCompareMode, tcu::Sampler::WrapMode wrapS,
                        tcu::Sampler::WrapMode wrapT, const MaybeTextureSwizzle &texSwizzle,
                        tcu::Sampler::FilterMode minFilter, tcu::Sampler::FilterMode magFilter, int baseLevel,
                        uint32_t flags, const IVec2 &textureSize)
        : TextureGatherCase(context, name, description, TEXTURETYPE_2D, gatherType, offsetSize, textureFormat,
                            shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags)
        , m_textureSize(textureSize)
        , m_swizzledTexture(tcu::TextureFormat(), 1, 1)
    {
    }

protected:
    void generateIterations(void);
    void createAndUploadTexture(void);
    int getNumIterations(void) const
    {
        DE_ASSERT(!m_iterations.empty());
        return (int)m_iterations.size();
    }
    GatherArgs getGatherArgs(int iterationNdx) const
    {
        return m_iterations[iterationNdx];
    }
    vector<float> computeQuadTexCoord(int iterationNdx) const;
    bool verify(int iterationNdx, const ConstPixelBufferAccess &rendered) const;

private:
    const IVec2 m_textureSize;

    MovePtr<glu::Texture2D> m_texture;
    tcu::Texture2D m_swizzledTexture;
    vector<GatherArgs> m_iterations;
};

vector<float> TextureGather2DCase::computeQuadTexCoord(int /* iterationNdx */) const
{
    vector<float> res;
    glu::TextureTestUtil::computeQuadTexCoord2D(res, Vec2(-0.3f, -0.4f), Vec2(1.5f, 1.6f));
    return res;
}

void TextureGather2DCase::generateIterations(void)
{
    DE_ASSERT(m_iterations.empty());
    m_iterations = generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());
}

void TextureGather2DCase::createAndUploadTexture(void)
{
    const glu::RenderContext &renderCtx     = m_context.getRenderContext();
    const glw::Functions &gl                = renderCtx.getFunctions();
    const tcu::TextureFormatInfo texFmtInfo = tcu::getTextureFormatInfo(m_textureFormat);

    m_texture = MovePtr<glu::Texture2D>(
        new glu::Texture2D(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize.x(), m_textureSize.y()));

    {
        tcu::Texture2D &refTexture = m_texture->getRefTexture();
        const int levelBegin       = m_baseLevel;
        const int levelEnd         = isMipmapFilter(m_minFilter) && !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ?
                                         refTexture.getNumLevels() :
                                         m_baseLevel + 1;
        DE_ASSERT(m_baseLevel < refTexture.getNumLevels());

        for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
        {
            refTexture.allocLevel(levelNdx);
            const PixelBufferAccess &level = refTexture.getLevel(levelNdx);
            fillWithRandomColorTiles(level, texFmtInfo.valueMin, texFmtInfo.valueMax,
                                     (uint32_t)m_testCtx.getCommandLine().getBaseSeed());
            m_testCtx.getLog() << TestLog::Image("InputTextureLevel" + de::toString(levelNdx),
                                                 "Input texture, level " + de::toString(levelNdx), level)
                               << TestLog::Message << "Note: texture level's size is "
                               << IVec2(level.getWidth(), level.getHeight()) << TestLog::EndMessage;
        }

        swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
    }

    gl.activeTexture(GL_TEXTURE0);
    m_texture->upload();
}

bool TextureGather2DCase::verify(int iterationNdx, const ConstPixelBufferAccess &rendered) const
{
    Vec2 texCoords[4];
    computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
    return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::Texture2DView(m_swizzledTexture), m_baseLevel),
                                     texCoords, m_iterations[iterationNdx]);
}

class TextureGather2DArrayCase : public TextureGatherCase
{
public:
    TextureGather2DArrayCase(Context &context, const char *name, const char *description, GatherType gatherType,
                             OffsetSize offsetSize, tcu::TextureFormat textureFormat,
                             tcu::Sampler::CompareMode shadowCompareMode, tcu::Sampler::WrapMode wrapS,
                             tcu::Sampler::WrapMode wrapT, const MaybeTextureSwizzle &texSwizzle,
                             tcu::Sampler::FilterMode minFilter, tcu::Sampler::FilterMode magFilter, int baseLevel,
                             uint32_t flags, const IVec3 &textureSize)
        : TextureGatherCase(context, name, description, TEXTURETYPE_2D_ARRAY, gatherType, offsetSize, textureFormat,
                            shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel, flags)
        , m_textureSize(textureSize)
        , m_swizzledTexture(tcu::TextureFormat(), 1, 1, 1)
    {
    }

protected:
    void generateIterations(void);
    void createAndUploadTexture(void);
    int getNumIterations(void) const
    {
        DE_ASSERT(!m_iterations.empty());
        return (int)m_iterations.size();
    }
    GatherArgs getGatherArgs(int iterationNdx) const
    {
        return m_iterations[iterationNdx].gatherArgs;
    }
    vector<float> computeQuadTexCoord(int iterationNdx) const;
    bool verify(int iterationNdx, const ConstPixelBufferAccess &rendered) const;

private:
    struct Iteration
    {
        GatherArgs gatherArgs;
        int layerNdx;
    };

    const IVec3 m_textureSize;

    MovePtr<glu::Texture2DArray> m_texture;
    tcu::Texture2DArray m_swizzledTexture;
    vector<Iteration> m_iterations;
};

vector<float> TextureGather2DArrayCase::computeQuadTexCoord(int iterationNdx) const
{
    vector<float> res;
    glu::TextureTestUtil::computeQuadTexCoord2DArray(res, m_iterations[iterationNdx].layerNdx, Vec2(-0.3f, -0.4f),
                                                     Vec2(1.5f, 1.6f));
    return res;
}

void TextureGather2DArrayCase::generateIterations(void)
{
    DE_ASSERT(m_iterations.empty());

    const vector<GatherArgs> basicIterations =
        generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());

    // \note Out-of-bounds layer indices are tested too.
    for (int layerNdx = -1; layerNdx < m_textureSize.z() + 1; layerNdx++)
    {
        // Don't duplicate all cases for all layers.
        if (layerNdx == 0)
        {
            for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
            {
                m_iterations.push_back(Iteration());
                m_iterations.back().gatherArgs = basicIterations[basicNdx];
                m_iterations.back().layerNdx   = layerNdx;
            }
        }
        else
        {
            // For other layers than 0, only test one component and one set of offsets per layer.
            for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
            {
                if (isDepthFormat(m_textureFormat) || basicIterations[basicNdx].componentNdx == (layerNdx + 2) % 4)
                {
                    m_iterations.push_back(Iteration());
                    m_iterations.back().gatherArgs = basicIterations[basicNdx];
                    m_iterations.back().layerNdx   = layerNdx;
                    break;
                }
            }
        }
    }
}

void TextureGather2DArrayCase::createAndUploadTexture(void)
{
    TestLog &log                            = m_testCtx.getLog();
    const glu::RenderContext &renderCtx     = m_context.getRenderContext();
    const glw::Functions &gl                = renderCtx.getFunctions();
    const tcu::TextureFormatInfo texFmtInfo = tcu::getTextureFormatInfo(m_textureFormat);

    m_texture = MovePtr<glu::Texture2DArray>(new glu::Texture2DArray(
        renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize.x(), m_textureSize.y(), m_textureSize.z()));

    {
        tcu::Texture2DArray &refTexture = m_texture->getRefTexture();
        const int levelBegin            = m_baseLevel;
        const int levelEnd              = isMipmapFilter(m_minFilter) && !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ?
                                              refTexture.getNumLevels() :
                                              m_baseLevel + 1;
        DE_ASSERT(m_baseLevel < refTexture.getNumLevels());

        for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
        {
            refTexture.allocLevel(levelNdx);
            const PixelBufferAccess &level = refTexture.getLevel(levelNdx);
            fillWithRandomColorTiles(level, texFmtInfo.valueMin, texFmtInfo.valueMax,
                                     (uint32_t)m_testCtx.getCommandLine().getBaseSeed());

            log << TestLog::ImageSet("InputTextureLevel", "Input texture, level " + de::toString(levelNdx));
            for (int layerNdx = 0; layerNdx < m_textureSize.z(); layerNdx++)
                log << TestLog::Image("InputTextureLevel" + de::toString(layerNdx) + "Layer" + de::toString(layerNdx),
                                      "Layer " + de::toString(layerNdx),
                                      tcu::getSubregion(level, 0, 0, layerNdx, level.getWidth(), level.getHeight(), 1));
            log << TestLog::EndImageSet << TestLog::Message << "Note: texture level's size is "
                << IVec3(level.getWidth(), level.getHeight(), level.getDepth()) << TestLog::EndMessage;
        }

        swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
    }

    gl.activeTexture(GL_TEXTURE0);
    m_texture->upload();
}

bool TextureGather2DArrayCase::verify(int iterationNdx, const ConstPixelBufferAccess &rendered) const
{
    Vec3 texCoords[4];
    computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
    return TextureGatherCase::verify(rendered,
                                     getOneLevelSubView(tcu::Texture2DArrayView(m_swizzledTexture), m_baseLevel),
                                     texCoords, m_iterations[iterationNdx].gatherArgs);
}

// \note Cube case always uses just basic textureGather(); offset versions are not defined for cube maps.
class TextureGatherCubeCase : public TextureGatherCase
{
public:
    TextureGatherCubeCase(Context &context, const char *name, const char *description, tcu::TextureFormat textureFormat,
                          tcu::Sampler::CompareMode shadowCompareMode, tcu::Sampler::WrapMode wrapS,
                          tcu::Sampler::WrapMode wrapT, const MaybeTextureSwizzle &texSwizzle,
                          tcu::Sampler::FilterMode minFilter, tcu::Sampler::FilterMode magFilter, int baseLevel,
                          uint32_t flags, int textureSize)
        : TextureGatherCase(context, name, description, TEXTURETYPE_CUBE, GATHERTYPE_BASIC, OFFSETSIZE_NONE,
                            textureFormat, shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel,
                            flags)
        , m_textureSize(textureSize)
        , m_swizzledTexture(tcu::TextureFormat(), 1)
    {
    }

protected:
    void generateIterations(void);
    void createAndUploadTexture(void);
    int getNumIterations(void) const
    {
        DE_ASSERT(!m_iterations.empty());
        return (int)m_iterations.size();
    }
    GatherArgs getGatherArgs(int iterationNdx) const
    {
        return m_iterations[iterationNdx].gatherArgs;
    }
    vector<float> computeQuadTexCoord(int iterationNdx) const;
    bool verify(int iterationNdx, const ConstPixelBufferAccess &rendered) const;

private:
    struct Iteration
    {
        GatherArgs gatherArgs;
        tcu::CubeFace face;
    };

    const int m_textureSize;

    MovePtr<glu::TextureCube> m_texture;
    tcu::TextureCube m_swizzledTexture;
    vector<Iteration> m_iterations;
};

vector<float> TextureGatherCubeCase::computeQuadTexCoord(int iterationNdx) const
{
    const bool corners = (m_flags & GATHERCASE_DONT_SAMPLE_CUBE_CORNERS) == 0;
    const Vec2 minC    = corners ? Vec2(-1.2f) : Vec2(-0.6f, -1.2f);
    const Vec2 maxC    = corners ? Vec2(1.2f) : Vec2(0.6f, 1.2f);
    vector<float> res;
    glu::TextureTestUtil::computeQuadTexCoordCube(res, m_iterations[iterationNdx].face, minC, maxC);
    return res;
}

void TextureGatherCubeCase::generateIterations(void)
{
    DE_ASSERT(m_iterations.empty());

    const vector<GatherArgs> basicIterations =
        generateBasic2DCaseIterations(m_gatherType, m_textureFormat, getOffsetRange());

    for (int cubeFaceI = 0; cubeFaceI < tcu::CUBEFACE_LAST; cubeFaceI++)
    {
        const tcu::CubeFace cubeFace = (tcu::CubeFace)cubeFaceI;

        // Don't duplicate all cases for all faces.
        if (cubeFaceI == 0)
        {
            for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
            {
                m_iterations.push_back(Iteration());
                m_iterations.back().gatherArgs = basicIterations[basicNdx];
                m_iterations.back().face       = cubeFace;
            }
        }
        else
        {
            // For other faces than first, only test one component per face.
            for (int basicNdx = 0; basicNdx < (int)basicIterations.size(); basicNdx++)
            {
                if (isDepthFormat(m_textureFormat) || basicIterations[basicNdx].componentNdx == cubeFaceI % 4)
                {
                    m_iterations.push_back(Iteration());
                    m_iterations.back().gatherArgs = basicIterations[basicNdx];
                    m_iterations.back().face       = cubeFace;
                    break;
                }
            }
        }
    }
}

void TextureGatherCubeCase::createAndUploadTexture(void)
{
    TestLog &log                            = m_testCtx.getLog();
    const glu::RenderContext &renderCtx     = m_context.getRenderContext();
    const glw::Functions &gl                = renderCtx.getFunctions();
    const tcu::TextureFormatInfo texFmtInfo = tcu::getTextureFormatInfo(m_textureFormat);

    m_texture = MovePtr<glu::TextureCube>(
        new glu::TextureCube(renderCtx, glu::getInternalFormat(m_textureFormat), m_textureSize));

    {
        tcu::TextureCube &refTexture = m_texture->getRefTexture();
        const int levelBegin         = m_baseLevel;
        const int levelEnd           = isMipmapFilter(m_minFilter) && !(m_flags & GATHERCASE_MIPMAP_INCOMPLETE) ?
                                           refTexture.getNumLevels() :
                                           m_baseLevel + 1;
        DE_ASSERT(m_baseLevel < refTexture.getNumLevels());

        for (int levelNdx = levelBegin; levelNdx < levelEnd; levelNdx++)
        {
            log << TestLog::ImageSet("InputTextureLevel" + de::toString(levelNdx),
                                     "Input texture, level " + de::toString(levelNdx));

            for (int cubeFaceI = 0; cubeFaceI < tcu::CUBEFACE_LAST; cubeFaceI++)
            {
                const tcu::CubeFace cubeFace = (tcu::CubeFace)cubeFaceI;
                refTexture.allocLevel(cubeFace, levelNdx);
                const PixelBufferAccess &levelFace = refTexture.getLevelFace(levelNdx, cubeFace);
                fillWithRandomColorTiles(levelFace, texFmtInfo.valueMin, texFmtInfo.valueMax,
                                         (uint32_t)m_testCtx.getCommandLine().getBaseSeed() ^ (uint32_t)cubeFaceI);

                m_testCtx.getLog() << TestLog::Image("InputTextureLevel" + de::toString(levelNdx) + "Face" +
                                                         de::toString((int)cubeFace),
                                                     de::toString(cubeFace), levelFace);
            }

            log << TestLog::EndImageSet << TestLog::Message << "Note: texture level's size is "
                << refTexture.getLevelFace(levelNdx, tcu::CUBEFACE_NEGATIVE_X).getWidth() << TestLog::EndMessage;
        }

        swizzleTexture(m_swizzledTexture, refTexture, m_textureSwizzle);
    }

    gl.activeTexture(GL_TEXTURE0);
    m_texture->upload();
}

bool TextureGatherCubeCase::verify(int iterationNdx, const ConstPixelBufferAccess &rendered) const
{
    Vec3 texCoords[4];
    computeTexCoordVecs(computeQuadTexCoord(iterationNdx), texCoords);
    return TextureGatherCase::verify(rendered, getOneLevelSubView(tcu::TextureCubeView(m_swizzledTexture), m_baseLevel),
                                     texCoords, m_iterations[iterationNdx].gatherArgs);
}

static inline TextureGatherCase *makeTextureGatherCase(
    TextureType textureType, Context &context, const char *name, const char *description, GatherType gatherType,
    OffsetSize offsetSize, tcu::TextureFormat textureFormat, tcu::Sampler::CompareMode shadowCompareMode,
    tcu::Sampler::WrapMode wrapS, tcu::Sampler::WrapMode wrapT, const MaybeTextureSwizzle &texSwizzle,
    tcu::Sampler::FilterMode minFilter, tcu::Sampler::FilterMode magFilter, int baseLevel, const IVec3 &textureSize,
    uint32_t flags = 0)
{
    switch (textureType)
    {
    case TEXTURETYPE_2D:
        return new TextureGather2DCase(context, name, description, gatherType, offsetSize, textureFormat,
                                       shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter, baseLevel,
                                       flags, textureSize.swizzle(0, 1));

    case TEXTURETYPE_2D_ARRAY:
        return new TextureGather2DArrayCase(context, name, description, gatherType, offsetSize, textureFormat,
                                            shadowCompareMode, wrapS, wrapT, texSwizzle, minFilter, magFilter,
                                            baseLevel, flags, textureSize);

    case TEXTURETYPE_CUBE:
        DE_ASSERT(gatherType == GATHERTYPE_BASIC);
        DE_ASSERT(offsetSize == OFFSETSIZE_NONE);
        return new TextureGatherCubeCase(context, name, description, textureFormat, shadowCompareMode, wrapS, wrapT,
                                         texSwizzle, minFilter, magFilter, baseLevel, flags, textureSize.x());

    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

} // namespace

TextureGatherTests::TextureGatherTests(Context &context) : TestCaseGroup(context, "gather", "textureGather* tests")
{
}

static inline const char *compareModeName(tcu::Sampler::CompareMode mode)
{
    switch (mode)
    {
    case tcu::Sampler::COMPAREMODE_LESS:
        return "less";
    case tcu::Sampler::COMPAREMODE_LESS_OR_EQUAL:
        return "less_or_equal";
    case tcu::Sampler::COMPAREMODE_GREATER:
        return "greater";
    case tcu::Sampler::COMPAREMODE_GREATER_OR_EQUAL:
        return "greater_or_equal";
    case tcu::Sampler::COMPAREMODE_EQUAL:
        return "equal";
    case tcu::Sampler::COMPAREMODE_NOT_EQUAL:
        return "not_equal";
    case tcu::Sampler::COMPAREMODE_ALWAYS:
        return "always";
    case tcu::Sampler::COMPAREMODE_NEVER:
        return "never";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

void TextureGatherTests::init(void)
{
    const struct
    {
        const char *name;
        TextureType type;
    } textureTypes[] = {{"2d", TEXTURETYPE_2D}, {"2d_array", TEXTURETYPE_2D_ARRAY}, {"cube", TEXTURETYPE_CUBE}};

    const struct
    {
        const char *name;
        tcu::TextureFormat format;
    } formats[] = {{"rgba8", tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8)},
                   {"rgba8ui", tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT8)},
                   {"rgba8i", tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT8)},
                   {"depth32f", tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT)}};

    const struct
    {
        const char *name;
        IVec3 size;
    } textureSizes[] = {{"size_pot", IVec3(64, 64, 3)}, {"size_npot", IVec3(17, 23, 3)}};

    const struct
    {
        const char *name;
        tcu::Sampler::WrapMode mode;
    } wrapModes[] = {{"clamp_to_edge", tcu::Sampler::CLAMP_TO_EDGE},
                     {"repeat", tcu::Sampler::REPEAT_GL},
                     {"mirrored_repeat", tcu::Sampler::MIRRORED_REPEAT_GL}};

    for (int gatherTypeI = 0; gatherTypeI < GATHERTYPE_LAST; gatherTypeI++)
    {
        const GatherType gatherType = (GatherType)gatherTypeI;
        TestCaseGroup *const gatherTypeGroup =
            new TestCaseGroup(m_context, gatherTypeName(gatherType), gatherTypeDescription(gatherType));
        addChild(gatherTypeGroup);

        for (int offsetSizeI = 0; offsetSizeI < OFFSETSIZE_LAST; offsetSizeI++)
        {
            const OffsetSize offsetSize = (OffsetSize)offsetSizeI;
            if ((gatherType == GATHERTYPE_BASIC) != (offsetSize == OFFSETSIZE_NONE))
                continue;

            TestCaseGroup *const offsetSizeGroup =
                offsetSize == OFFSETSIZE_NONE ?
                    gatherTypeGroup :
                    new TestCaseGroup(m_context,
                                      offsetSize == OFFSETSIZE_MINIMUM_REQUIRED       ? "min_required_offset" :
                                      offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM ? "implementation_offset" :
                                                                                        DE_NULL,
                                      offsetSize == OFFSETSIZE_MINIMUM_REQUIRED ?
                                          "Use offsets within GL minimum required range" :
                                      offsetSize == OFFSETSIZE_IMPLEMENTATION_MAXIMUM ?
                                          "Use offsets within the implementation range" :
                                          DE_NULL);
            if (offsetSizeGroup != gatherTypeGroup)
                gatherTypeGroup->addChild(offsetSizeGroup);

            for (int textureTypeNdx = 0; textureTypeNdx < DE_LENGTH_OF_ARRAY(textureTypes); textureTypeNdx++)
            {
                const TextureType textureType = textureTypes[textureTypeNdx].type;

                if (textureType == TEXTURETYPE_CUBE && gatherType != GATHERTYPE_BASIC)
                    continue;

                TestCaseGroup *const textureTypeGroup =
                    new TestCaseGroup(m_context, textureTypes[textureTypeNdx].name, "");
                offsetSizeGroup->addChild(textureTypeGroup);

                for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); formatNdx++)
                {
                    const tcu::TextureFormat &format = formats[formatNdx].format;
                    TestCaseGroup *const formatGroup = new TestCaseGroup(m_context, formats[formatNdx].name, "");
                    textureTypeGroup->addChild(formatGroup);

                    for (int noCornersI = 0; noCornersI <= ((textureType == TEXTURETYPE_CUBE) ? 1 : 0); noCornersI++)
                    {
                        const bool noCorners = noCornersI != 0;
                        TestCaseGroup *const cornersGroup =
                            noCorners ?
                                new TestCaseGroup(m_context, "no_corners",
                                                  "Test case variants that don't sample around cube map corners") :
                                formatGroup;

                        if (formatGroup != cornersGroup)
                            formatGroup->addChild(cornersGroup);

                        for (int textureSizeNdx = 0; textureSizeNdx < DE_LENGTH_OF_ARRAY(textureSizes);
                             textureSizeNdx++)
                        {
                            const IVec3 &textureSize = textureSizes[textureSizeNdx].size;
                            TestCaseGroup *const textureSizeGroup =
                                new TestCaseGroup(m_context, textureSizes[textureSizeNdx].name, "");
                            cornersGroup->addChild(textureSizeGroup);

                            for (int compareModeI = 0; compareModeI < tcu::Sampler::COMPAREMODE_LAST; compareModeI++)
                            {
                                const tcu::Sampler::CompareMode compareMode = (tcu::Sampler::CompareMode)compareModeI;

                                if ((compareMode != tcu::Sampler::COMPAREMODE_NONE) != isDepthFormat(format))
                                    continue;

                                if (compareMode != tcu::Sampler::COMPAREMODE_NONE &&
                                    compareMode != tcu::Sampler::COMPAREMODE_LESS &&
                                    compareMode != tcu::Sampler::COMPAREMODE_GREATER)
                                    continue;

                                TestCaseGroup *const compareModeGroup =
                                    compareMode == tcu::Sampler::COMPAREMODE_NONE ?
                                        textureSizeGroup :
                                        new TestCaseGroup(
                                            m_context, (string() + "compare_" + compareModeName(compareMode)).c_str(),
                                            "");
                                if (compareModeGroup != textureSizeGroup)
                                    textureSizeGroup->addChild(compareModeGroup);

                                for (int wrapCaseNdx = 0; wrapCaseNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapCaseNdx++)
                                {
                                    const int wrapSNdx = wrapCaseNdx;
                                    const int wrapTNdx = (wrapCaseNdx + 1) % DE_LENGTH_OF_ARRAY(wrapModes);
                                    const tcu::Sampler::WrapMode wrapS = wrapModes[wrapSNdx].mode;
                                    const tcu::Sampler::WrapMode wrapT = wrapModes[wrapTNdx].mode;

                                    const string caseName =
                                        string() + wrapModes[wrapSNdx].name + "_" + wrapModes[wrapTNdx].name;

                                    compareModeGroup->addChild(makeTextureGatherCase(
                                        textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
                                        compareMode, wrapS, wrapT, MaybeTextureSwizzle::createNoneTextureSwizzle(),
                                        tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0, textureSize,
                                        noCorners ? GATHERCASE_DONT_SAMPLE_CUBE_CORNERS : 0));
                                }
                            }
                        }
                    }

                    if (offsetSize !=
                        OFFSETSIZE_MINIMUM_REQUIRED) // Don't test all features for both offset size types, as they should be rather orthogonal.
                    {
                        if (!isDepthFormat(format))
                        {
                            TestCaseGroup *const swizzleGroup = new TestCaseGroup(m_context, "texture_swizzle", "");
                            formatGroup->addChild(swizzleGroup);

                            DE_STATIC_ASSERT(TEXTURESWIZZLECOMPONENT_R == 0);
                            for (int swizzleCaseNdx = 0; swizzleCaseNdx < TEXTURESWIZZLECOMPONENT_LAST;
                                 swizzleCaseNdx++)
                            {
                                MaybeTextureSwizzle swizzle = MaybeTextureSwizzle::createSomeTextureSwizzle();
                                string caseName;

                                for (int i = 0; i < 4; i++)
                                {
                                    swizzle.getSwizzle()[i] =
                                        (TextureSwizzleComponent)((swizzleCaseNdx + i) %
                                                                  (int)TEXTURESWIZZLECOMPONENT_LAST);
                                    caseName += (i > 0 ? "_" : "") + de::toLower(de::toString(swizzle.getSwizzle()[i]));
                                }

                                swizzleGroup->addChild(makeTextureGatherCase(
                                    textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
                                    tcu::Sampler::COMPAREMODE_NONE, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
                                    swizzle, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0, IVec3(64, 64, 3)));
                            }
                        }

                        {
                            TestCaseGroup *const filterModeGroup =
                                new TestCaseGroup(m_context, "filter_mode", "Test that filter modes have no effect");
                            formatGroup->addChild(filterModeGroup);

                            const struct
                            {
                                const char *name;
                                tcu::Sampler::FilterMode filter;
                            } magFilters[] = {{"linear", tcu::Sampler::LINEAR}, {"nearest", tcu::Sampler::NEAREST}};

                            const struct
                            {
                                const char *name;
                                tcu::Sampler::FilterMode filter;
                            } minFilters[] = {
                                // \note Don't test NEAREST here, as it's covered by other cases.
                                {"linear", tcu::Sampler::LINEAR},
                                {"nearest_mipmap_nearest", tcu::Sampler::NEAREST_MIPMAP_NEAREST},
                                {"nearest_mipmap_linear", tcu::Sampler::NEAREST_MIPMAP_LINEAR},
                                {"linear_mipmap_nearest", tcu::Sampler::LINEAR_MIPMAP_NEAREST},
                                {"linear_mipmap_linear", tcu::Sampler::LINEAR_MIPMAP_LINEAR},
                            };

                            for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilters); minFilterNdx++)
                                for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilters);
                                     magFilterNdx++)
                                {
                                    const tcu::Sampler::FilterMode minFilter    = minFilters[minFilterNdx].filter;
                                    const tcu::Sampler::FilterMode magFilter    = magFilters[magFilterNdx].filter;
                                    const tcu::Sampler::CompareMode compareMode = isDepthFormat(format) ?
                                                                                      tcu::Sampler::COMPAREMODE_LESS :
                                                                                      tcu::Sampler::COMPAREMODE_NONE;

                                    if ((isUnormFormatType(format.type) || isDepthFormat(format)) &&
                                        magFilter == tcu::Sampler::NEAREST)
                                        continue; // Covered by other cases.
                                    if ((isUIntFormatType(format.type) || isSIntFormatType(format.type)) &&
                                        (magFilter != tcu::Sampler::NEAREST ||
                                         minFilter != tcu::Sampler::NEAREST_MIPMAP_NEAREST))
                                        continue;

                                    const string caseName = string() + "min_" + minFilters[minFilterNdx].name +
                                                            "_mag_" + magFilters[magFilterNdx].name;

                                    filterModeGroup->addChild(makeTextureGatherCase(
                                        textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
                                        compareMode, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
                                        MaybeTextureSwizzle::createNoneTextureSwizzle(), minFilter, magFilter, 0,
                                        IVec3(64, 64, 3)));
                                }
                        }

                        {
                            TestCaseGroup *const baseLevelGroup = new TestCaseGroup(m_context, "base_level", "");
                            formatGroup->addChild(baseLevelGroup);

                            for (int baseLevel = 1; baseLevel <= 2; baseLevel++)
                            {
                                const string caseName                       = "level_" + de::toString(baseLevel);
                                const tcu::Sampler::CompareMode compareMode = isDepthFormat(format) ?
                                                                                  tcu::Sampler::COMPAREMODE_LESS :
                                                                                  tcu::Sampler::COMPAREMODE_NONE;
                                baseLevelGroup->addChild(makeTextureGatherCase(
                                    textureType, m_context, caseName.c_str(), "", gatherType, offsetSize, format,
                                    compareMode, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
                                    MaybeTextureSwizzle::createNoneTextureSwizzle(), tcu::Sampler::NEAREST,
                                    tcu::Sampler::NEAREST, baseLevel, IVec3(64, 64, 3)));
                            }
                        }

                        // What shadow textures should return for incomplete textures is unclear.
                        // Integer and unsigned integer lookups from incomplete textures return undefined values.
                        if (!isDepthFormat(format) && !isSIntFormatType(format.type) && !isUIntFormatType(format.type))
                        {
                            TestCaseGroup *const incompleteGroup = new TestCaseGroup(
                                m_context, "incomplete",
                                "Test that textureGather* takes components from (0,0,0,1) for incomplete textures");
                            formatGroup->addChild(incompleteGroup);

                            const tcu::Sampler::CompareMode compareMode =
                                isDepthFormat(format) ? tcu::Sampler::COMPAREMODE_LESS : tcu::Sampler::COMPAREMODE_NONE;
                            incompleteGroup->addChild(makeTextureGatherCase(
                                textureType, m_context, "mipmap_incomplete", "", gatherType, offsetSize, format,
                                compareMode, tcu::Sampler::REPEAT_GL, tcu::Sampler::REPEAT_GL,
                                MaybeTextureSwizzle::createNoneTextureSwizzle(), tcu::Sampler::NEAREST_MIPMAP_NEAREST,
                                tcu::Sampler::NEAREST, 0, IVec3(64, 64, 3), GATHERCASE_MIPMAP_INCOMPLETE));
                        }
                    }
                }
            }
        }
    }
}

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