/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Texture border clamp tests.
 *//*--------------------------------------------------------------------*/

#include "es31fTextureBorderClampTests.hpp"

#include "glsTextureTestUtil.hpp"

#include "tcuTextureUtil.hpp"
#include "tcuTexLookupVerifier.hpp"
#include "tcuTexCompareVerifier.hpp"
#include "tcuCompressedTexture.hpp"
#include "tcuResultCollector.hpp"
#include "tcuSurface.hpp"
#include "tcuSeedBuilder.hpp"
#include "tcuVectorUtil.hpp"

#include "rrGenericVector.hpp"

#include "gluContextInfo.hpp"
#include "gluTexture.hpp"
#include "gluTextureUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluStrUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "gluDrawUtil.hpp"

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

#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include "deRandom.hpp"

#include <limits>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

enum SizeType
{
    SIZE_POT = 0,
    SIZE_NPOT
};

bool filterRequiresFilterability(uint32_t filter)
{
    switch (filter)
    {
    case GL_NEAREST:
    case GL_NEAREST_MIPMAP_NEAREST:
        return false;

    case GL_LINEAR:
    case GL_LINEAR_MIPMAP_NEAREST:
    case GL_NEAREST_MIPMAP_LINEAR:
    case GL_LINEAR_MIPMAP_LINEAR:
        return true;

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

bool isDepthFormat(uint32_t format, tcu::Sampler::DepthStencilMode mode)
{
    if (format == GL_LUMINANCE || format == GL_LUMINANCE_ALPHA || format == GL_ALPHA || format == GL_BGRA)
    {
        // Unsized formats are a special case
        return false;
    }
    else if (glu::isCompressedFormat(format))
    {
        // no known compressed depth formats
        return false;
    }
    else
    {
        const tcu::TextureFormat fmt = glu::mapGLInternalFormat(format);

        if (fmt.order == tcu::TextureFormat::D)
        {
            DE_ASSERT(mode == tcu::Sampler::MODE_DEPTH);
            return true;
        }
        else if (fmt.order == tcu::TextureFormat::DS && mode == tcu::Sampler::MODE_DEPTH)
            return true;
        else
            return false;
    }
}

bool isStencilFormat(uint32_t format, tcu::Sampler::DepthStencilMode mode)
{
    if (format == GL_LUMINANCE || format == GL_LUMINANCE_ALPHA || format == GL_ALPHA || format == GL_BGRA)
    {
        // Unsized formats are a special case
        return false;
    }
    else if (glu::isCompressedFormat(format))
    {
        // no known compressed stencil formats
        return false;
    }
    else
    {
        const tcu::TextureFormat fmt = glu::mapGLInternalFormat(format);

        if (fmt.order == tcu::TextureFormat::S)
        {
            DE_ASSERT(mode == tcu::Sampler::MODE_STENCIL);
            return true;
        }
        else if (fmt.order == tcu::TextureFormat::DS && mode == tcu::Sampler::MODE_STENCIL)
            return true;
        else
            return false;
    }
}

tcu::TextureChannelClass getFormatChannelClass(uint32_t format, tcu::Sampler::DepthStencilMode mode)
{
    if (format == GL_LUMINANCE || format == GL_LUMINANCE_ALPHA || format == GL_ALPHA || format == GL_BGRA)
    {
        // Unsized formats are a special c, use UNORM8
        return tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
    }
    else if (glu::isCompressedFormat(format))
    {
        const tcu::CompressedTexFormat compressedFmt = glu::mapGLCompressedTexFormat(format);
        const tcu::TextureFormat uncompressedFmt     = tcu::getUncompressedFormat(compressedFmt);
        return tcu::getTextureChannelClass(uncompressedFmt.type);
    }
    else
    {
        const tcu::TextureFormat fmt          = glu::mapGLInternalFormat(format);
        const tcu::TextureFormat effectiveFmt = tcu::getEffectiveDepthStencilTextureFormat(fmt, mode);

        return tcu::getTextureChannelClass(effectiveFmt.type);
    }
}

int getDimensionNumBlocks(int dimensionSize, int blockSize)
{
    // ceil( a / b )
    return (dimensionSize + blockSize - 1) / blockSize;
}

void generateDefaultCompressedData(tcu::CompressedTexture &dst, const tcu::CompressedTexFormat &format)
{
    const int blockByteSize         = tcu::getBlockSize(format);
    const tcu::IVec3 blockPixelSize = tcu::getBlockPixelSize(format);
    const tcu::IVec3 numBlocks(getDimensionNumBlocks(dst.getWidth(), blockPixelSize.x()),
                               getDimensionNumBlocks(dst.getHeight(), blockPixelSize.y()),
                               getDimensionNumBlocks(dst.getDepth(), blockPixelSize.z()));
    const int numTotalBlocks = numBlocks.x() * numBlocks.y() * numBlocks.z();
    const int dataSize       = numTotalBlocks * blockByteSize;

    DE_ASSERT(dst.getDataSize() == dataSize);

    if (tcu::isAstcFormat(format))
    {
        // generate data that is valid in LDR mode
        const int BLOCK_SIZE            = 16;
        const uint8_t block[BLOCK_SIZE] = {252, 253, 255, 255, 255, 255, 255, 255,
                                           223, 251, 28,  206, 54,  251, 160, 174};

        DE_ASSERT(blockByteSize == BLOCK_SIZE);
        for (int ndx = 0; ndx < numTotalBlocks; ++ndx)
            deMemcpy((uint8_t *)dst.getData() + ndx * BLOCK_SIZE, block, BLOCK_SIZE);
    }
    else
    {
        // any data is ok
        de::Random rnd(0xabc);

        for (int ndx = 0; ndx < dataSize; ++ndx)
            ((uint8_t *)dst.getData())[ndx] = rnd.getUint8();
    }
}

template <typename T>
struct TextureTraits
{
};

template <>
struct TextureTraits<glu::Texture2D>
{
    typedef tcu::IVec2 SizeType;

    static de::MovePtr<glu::Texture2D> createTextureFromInternalFormat(glu::RenderContext &renderCtx,
                                                                       uint32_t texFormat, const tcu::IVec2 &size)
    {
        return de::MovePtr<glu::Texture2D>(new glu::Texture2D(renderCtx, texFormat, size.x(), size.y()));
    }

    static de::MovePtr<glu::Texture2D> createTextureFromFormatAndType(glu::RenderContext &renderCtx, uint32_t texFormat,
                                                                      uint32_t type, const tcu::IVec2 &size)
    {
        return de::MovePtr<glu::Texture2D>(new glu::Texture2D(renderCtx, texFormat, type, size.x(), size.y()));
    }

    static de::MovePtr<glu::Texture2D> createTextureFromCompressedData(
        glu::RenderContext &renderCtx, const glu::ContextInfo &ctxInfo, const tcu::CompressedTexture &compressedLevel,
        const tcu::TexDecompressionParams &decompressionParams)
    {
        return de::MovePtr<glu::Texture2D>(
            new glu::Texture2D(renderCtx, ctxInfo, 1, &compressedLevel, decompressionParams));
    }

    static int getTextureNumLayers(const tcu::IVec2 &size)
    {
        // 2D textures have one layer
        DE_UNREF(size);
        return 1;
    }
};

template <>
struct TextureTraits<glu::Texture3D>
{
    typedef tcu::IVec3 SizeType;

    static de::MovePtr<glu::Texture3D> createTextureFromInternalFormat(glu::RenderContext &renderCtx,
                                                                       uint32_t texFormat, const tcu::IVec3 &size)
    {
        return de::MovePtr<glu::Texture3D>(new glu::Texture3D(renderCtx, texFormat, size.x(), size.y(), size.z()));
    }

    static de::MovePtr<glu::Texture3D> createTextureFromFormatAndType(glu::RenderContext &renderCtx, uint32_t texFormat,
                                                                      uint32_t type, const tcu::IVec3 &size)
    {
        return de::MovePtr<glu::Texture3D>(
            new glu::Texture3D(renderCtx, texFormat, type, size.x(), size.y(), size.z()));
    }

    static de::MovePtr<glu::Texture3D> createTextureFromCompressedData(
        glu::RenderContext &renderCtx, const glu::ContextInfo &ctxInfo, const tcu::CompressedTexture &compressedLevel,
        const tcu::TexDecompressionParams &decompressionParams)
    {
        return de::MovePtr<glu::Texture3D>(
            new glu::Texture3D(renderCtx, ctxInfo, 1, &compressedLevel, decompressionParams));
    }

    static int getTextureNumLayers(const tcu::IVec3 &size)
    {
        // 3D textures have Z layers
        return size.z();
    }
};

template <typename T>
de::MovePtr<T> genDefaultTexture(glu::RenderContext &renderCtx, const glu::ContextInfo &ctxInfo, uint32_t texFormat,
                                 const typename TextureTraits<T>::SizeType &size)
{
    de::MovePtr<T> texture;

    if (isDepthFormat(texFormat, tcu::Sampler::MODE_DEPTH) || isStencilFormat(texFormat, tcu::Sampler::MODE_STENCIL))
    {
        // fill different channels with different gradients
        texture = TextureTraits<T>::createTextureFromInternalFormat(renderCtx, texFormat, size);
        texture->getRefTexture().allocLevel(0);

        if (isDepthFormat(texFormat, tcu::Sampler::MODE_DEPTH))
        {
            // fill depth with 0 -> 1
            const tcu::PixelBufferAccess depthAccess =
                tcu::getEffectiveDepthStencilAccess(texture->getRefTexture().getLevel(0), tcu::Sampler::MODE_DEPTH);
            tcu::fillWithComponentGradients(depthAccess, tcu::Vec4(0.0f), tcu::Vec4(1.0f));
        }

        if (isStencilFormat(texFormat, tcu::Sampler::MODE_STENCIL))
        {
            // fill stencil with 0 -> max
            const tcu::PixelBufferAccess stencilAccess =
                tcu::getEffectiveDepthStencilAccess(texture->getRefTexture().getLevel(0), tcu::Sampler::MODE_STENCIL);
            const tcu::TextureFormatInfo texFormatInfo = tcu::getTextureFormatInfo(stencilAccess.getFormat());

            // Flip y to make stencil and depth cases not look identical
            tcu::fillWithComponentGradients(tcu::flipYAccess(stencilAccess), texFormatInfo.valueMax,
                                            texFormatInfo.valueMin);
        }

        texture->upload();
    }
    else if (!glu::isCompressedFormat(texFormat))
    {
        if (texFormat == GL_LUMINANCE || texFormat == GL_LUMINANCE_ALPHA || texFormat == GL_ALPHA ||
            texFormat == GL_BGRA)
            texture = TextureTraits<T>::createTextureFromFormatAndType(renderCtx, texFormat, GL_UNSIGNED_BYTE, size);
        else
            texture = TextureTraits<T>::createTextureFromInternalFormat(renderCtx, texFormat, size);

        // Fill level 0.
        texture->getRefTexture().allocLevel(0);

        // fill with gradient min -> max
        {
            const tcu::TextureFormatInfo texFormatInfo =
                tcu::getTextureFormatInfo(texture->getRefTexture().getFormat());
            const tcu::Vec4 rampLow  = texFormatInfo.valueMin;
            const tcu::Vec4 rampHigh = texFormatInfo.valueMax;
            tcu::fillWithComponentGradients(texture->getRefTexture().getLevel(0), rampLow, rampHigh);
        }

        texture->upload();
    }
    else
    {
        const tcu::CompressedTexFormat compressedFormat = glu::mapGLCompressedTexFormat(texFormat);
        const int numLayers                             = TextureTraits<T>::getTextureNumLayers(size);
        tcu::CompressedTexture compressedLevel(compressedFormat, size.x(), size.y(), numLayers);
        const bool isAstcFormat = tcu::isAstcFormat(compressedFormat);
        tcu::TexDecompressionParams decompressionParams((isAstcFormat) ? (tcu::TexDecompressionParams::ASTCMODE_LDR) :
                                                                         (tcu::TexDecompressionParams::ASTCMODE_LAST));

        generateDefaultCompressedData(compressedLevel, compressedFormat);

        texture =
            TextureTraits<T>::createTextureFromCompressedData(renderCtx, ctxInfo, compressedLevel, decompressionParams);
    }

    return texture;
}

int getNBitIntegerMaxValue(bool isSigned, int numBits)
{
    DE_ASSERT(numBits < 32);

    if (numBits == 0)
        return 0;
    else if (isSigned)
        return deIntMaxValue32(numBits);
    else
        return deUintMaxValue32(numBits);
}

int getNBitIntegerMinValue(bool isSigned, int numBits)
{
    DE_ASSERT(numBits < 32);

    if (numBits == 0)
        return 0;
    else if (isSigned)
        return deIntMinValue32(numBits);
    else
        return 0;
}

tcu::IVec4 getNBitIntegerVec4MaxValue(bool isSigned, const tcu::IVec4 &numBits)
{
    return tcu::IVec4(getNBitIntegerMaxValue(isSigned, numBits[0]), getNBitIntegerMaxValue(isSigned, numBits[1]),
                      getNBitIntegerMaxValue(isSigned, numBits[2]), getNBitIntegerMaxValue(isSigned, numBits[3]));
}

tcu::IVec4 getNBitIntegerVec4MinValue(bool isSigned, const tcu::IVec4 &numBits)
{
    return tcu::IVec4(getNBitIntegerMinValue(isSigned, numBits[0]), getNBitIntegerMinValue(isSigned, numBits[1]),
                      getNBitIntegerMinValue(isSigned, numBits[2]), getNBitIntegerMinValue(isSigned, numBits[3]));
}

rr::GenericVec4 mapToFormatColorUnits(const tcu::TextureFormat &texFormat, const tcu::Vec4 &normalizedRange)
{
    const tcu::TextureFormatInfo texFormatInfo = tcu::getTextureFormatInfo(texFormat);

    switch (tcu::getTextureChannelClass(texFormat.type))
    {
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
        return rr::GenericVec4(normalizedRange);
    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
        return rr::GenericVec4(normalizedRange * 2.0f - 1.0f);
    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
        return rr::GenericVec4(texFormatInfo.valueMin + normalizedRange * texFormatInfo.valueMax);
    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
        return rr::GenericVec4(
            tcu::mix(texFormatInfo.valueMin, texFormatInfo.valueMax, normalizedRange).cast<int32_t>());
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
        return rr::GenericVec4(
            tcu::mix(texFormatInfo.valueMin, texFormatInfo.valueMax, normalizedRange).cast<uint32_t>());

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

rr::GenericVec4 mapToFormatColorRepresentable(const tcu::TextureFormat &texFormat, const tcu::Vec4 &normalizedRange)
{
    // make sure value is representable in the target format and clear channels
    // not present in the target format.

    const rr::GenericVec4 inFormatUnits = mapToFormatColorUnits(texFormat, normalizedRange);
    const tcu::BVec4 channelMask        = tcu::getTextureFormatChannelMask(texFormat);
    de::ArrayBuffer<uint8_t, 4> buffer(texFormat.getPixelSize());
    tcu::PixelBufferAccess access(texFormat, tcu::IVec3(1, 1, 1), buffer.getPtr());

    if (tcu::isSRGB(texFormat))
    {
        DE_ASSERT(texFormat.type == tcu::TextureFormat::UNORM_INT8);

        // make sure border color (in linear space) can be converted to 8-bit sRGB space without
        // significant loss.
        const tcu::Vec4 sRGB   = tcu::linearToSRGB(normalizedRange);
        const tcu::IVec4 sRGB8 = tcu::IVec4(tcu::floatToU8(sRGB[0]), tcu::floatToU8(sRGB[1]), tcu::floatToU8(sRGB[2]),
                                            tcu::floatToU8(sRGB[3]));
        const tcu::Vec4 linearized = tcu::sRGBToLinear(tcu::Vec4((float)sRGB8[0] / 255.0f, (float)sRGB8[1] / 255.0f,
                                                                 (float)sRGB8[2] / 255.0f, (float)sRGB8[3] / 255.0f));

        return rr::GenericVec4(tcu::select(linearized, tcu::Vec4(0.0f), channelMask));
    }

    switch (tcu::getTextureChannelClass(texFormat.type))
    {
    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
    {
        access.setPixel(inFormatUnits.get<float>(), 0, 0);
        return rr::GenericVec4(tcu::select(access.getPixel(0, 0), tcu::Vec4(0.0f), channelMask));
    }
    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
    {
        access.setPixel(inFormatUnits.get<int32_t>(), 0, 0);
        return rr::GenericVec4(tcu::select(access.getPixelInt(0, 0), tcu::IVec4(0), channelMask));
    }
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
    {
        access.setPixel(inFormatUnits.get<uint32_t>(), 0, 0);
        return rr::GenericVec4(tcu::select(access.getPixelUint(0, 0), tcu::UVec4(0u), channelMask));
    }
    default:
    {
        DE_ASSERT(false);
        return rr::GenericVec4();
    }
    }
}

bool isCoreFilterableFormat(uint32_t format, tcu::Sampler::DepthStencilMode mode,
                            bool is_texture_float_linear_supported = true)
{
    const bool isLuminanceOrAlpha   = (format == GL_LUMINANCE || format == GL_ALPHA ||
                                     format == GL_LUMINANCE_ALPHA); // special case for luminance/alpha
    const bool isUnsizedColorFormat = (format == GL_BGRA);
    const bool isCompressed         = glu::isCompressedFormat(format);
    const bool isDepth              = isDepthFormat(format, mode);
    const bool isStencil            = isStencilFormat(format, mode);
    const bool isFP32Format = (format == GL_RGBA32F || format == GL_RGB32F || format == GL_RG32F || format == GL_R32F);

    // special cases
    if (isLuminanceOrAlpha || isUnsizedColorFormat || isCompressed)
        return true;
    if (isStencil || isDepth)
        return false;
    if (isFP32Format)
        return is_texture_float_linear_supported;

    // color case
    return glu::isGLInternalColorFormatFilterable(format);
}

class TextureBorderClampTest : public TestCase
{
public:
    enum StateType
    {
        STATE_SAMPLER_PARAM = 0,
        STATE_TEXTURE_PARAM,

        STATE_LAST
    };

    enum SamplingFunction
    {
        SAMPLE_FILTER = 0,
        SAMPLE_GATHER,

        SAMPLE_LAST
    };

    enum Flag
    {
        FLAG_USE_SHADOW_SAMPLER    = (1u << 0),
        FLAG_TEST_FLOAT_FILTERABLE = (1u << 1),
    };

    struct IterationConfig
    {
        tcu::Vec2 p0;
        tcu::Vec2 p1;
        rr::GenericVec4 borderColor;
        tcu::Vec4 lookupScale;
        tcu::Vec4 lookupBias;
        uint32_t minFilter;
        uint32_t magFilter;
        std::string description;
        uint32_t sWrapMode;
        uint32_t tWrapMode;
        uint32_t compareMode;
        float compareRef;
    };

    TextureBorderClampTest(Context &context, const char *name, const char *description, uint32_t texFormat,
                           tcu::Sampler::DepthStencilMode mode, StateType stateType, int texWidth, int texHeight,
                           SamplingFunction samplingFunction, uint32_t filter, uint32_t flags = 0);
    ~TextureBorderClampTest(void);

protected:
    void init(void);
    void deinit(void);

private:
    IterateResult iterate(void);

    void logParams(const IterationConfig &config, const glu::TextureTestUtil::ReferenceParams &samplerParams);

    void renderTo(tcu::Surface &surface, const IterationConfig &config,
                  const glu::TextureTestUtil::ReferenceParams &samplerParams);
    void renderQuad(const float *texCoord, const glu::TextureTestUtil::ReferenceParams &samplerParams);

    void verifyImage(const tcu::Surface &image, const IterationConfig &config,
                     const glu::TextureTestUtil::ReferenceParams &samplerParams);

    bool verifyTextureSampleResult(const tcu::ConstPixelBufferAccess &renderedFrame, const float *texCoord,
                                   const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                   const tcu::LodPrecision &lodPrecision, const tcu::LookupPrecision &lookupPrecision);

    bool verifyTextureCompareResult(const tcu::ConstPixelBufferAccess &renderedFrame, const float *texCoord,
                                    const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                    const tcu::TexComparePrecision &texComparePrecision,
                                    const tcu::TexComparePrecision &lowQualityTexComparePrecision,
                                    const tcu::LodPrecision &lodPrecision,
                                    const tcu::LodPrecision &lowQualityLodPrecision);

    bool verifyTextureGatherResult(const tcu::ConstPixelBufferAccess &renderedFrame, const float *texCoord,
                                   const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                   const tcu::LookupPrecision &lookupPrecision);

    bool verifyTextureGatherCmpResult(const tcu::ConstPixelBufferAccess &renderedFrame, const float *texCoord,
                                      const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                      const tcu::TexComparePrecision &texComparePrecision,
                                      const tcu::TexComparePrecision &lowQualityTexComparePrecision);

    uint32_t getIterationSeed(const IterationConfig &config) const;
    glu::TextureTestUtil::ReferenceParams genSamplerParams(const IterationConfig &config) const;
    glu::ShaderProgram *genGatherProgram(void) const;

    virtual int getNumIterations(void) const            = 0;
    virtual IterationConfig getIteration(int ndx) const = 0;

protected:
    const glu::Texture2D *getTexture(void) const;

    const uint32_t m_texFormat;
    const tcu::Sampler::DepthStencilMode m_sampleMode;
    const tcu::TextureChannelClass m_channelClass;
    const StateType m_stateType;

    const int m_texHeight;
    const int m_texWidth;

    const SamplingFunction m_samplingFunction;
    const bool m_useShadowSampler;
    const bool m_useFloatFilterable;

    const uint32_t m_filter;

private:
    enum
    {
        VIEWPORT_WIDTH  = 128,
        VIEWPORT_HEIGHT = 128,
    };

    de::MovePtr<glu::Texture2D> m_texture;
    de::MovePtr<gls::TextureTestUtil::TextureRenderer> m_renderer;
    de::MovePtr<glu::ShaderProgram> m_gatherProgram;

    int m_iterationNdx;
    tcu::ResultCollector m_result;
};

TextureBorderClampTest::TextureBorderClampTest(Context &context, const char *name, const char *description,
                                               uint32_t texFormat, tcu::Sampler::DepthStencilMode mode,
                                               StateType stateType, int texWidth, int texHeight,
                                               SamplingFunction samplingFunction, uint32_t filter, uint32_t flags)
    : TestCase(context, name, description)
    , m_texFormat(texFormat)
    , m_sampleMode(mode)
    , m_channelClass(getFormatChannelClass(texFormat, mode))
    , m_stateType(stateType)
    , m_texHeight(texHeight)
    , m_texWidth(texWidth)
    , m_samplingFunction(samplingFunction)
    , m_useShadowSampler((flags & FLAG_USE_SHADOW_SAMPLER) != 0)
    , m_useFloatFilterable((flags & FLAG_TEST_FLOAT_FILTERABLE) != 0)
    , m_filter(filter)
    , m_iterationNdx(0)
    , m_result(context.getTestContext().getLog())
{
    DE_ASSERT(stateType < STATE_LAST);
    DE_ASSERT(samplingFunction < SAMPLE_LAST);
    // mode must be set for combined depth-stencil formats
    DE_ASSERT(m_channelClass != tcu::TEXTURECHANNELCLASS_LAST || mode != tcu::Sampler::MODE_LAST);
}

TextureBorderClampTest::~TextureBorderClampTest(void)
{
    deinit();
}

void TextureBorderClampTest::init(void)
{
    // requirements
    const bool supportsGL45 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) || supportsGL45;

    // repeat filterable test with valid context
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const bool is_texture_float_linear_supported =
        glu::hasExtension(gl, glu::ApiType::es(3, 0), "GL_OES_texture_float_linear");
    const bool coreFilterable = isCoreFilterableFormat(m_texFormat, m_sampleMode, is_texture_float_linear_supported);

    if (m_useFloatFilterable && !coreFilterable && filterRequiresFilterability(m_filter))
        throw tcu::NotSupportedError("Test requires GL_OES_texture_float_linear extension");

    if (!supportsES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_border_clamp"))
        throw tcu::NotSupportedError("Test requires GL_EXT_texture_border_clamp extension");

    if (glu::isCompressedFormat(m_texFormat) && !supportsES32orGL45 &&
        tcu::isAstcFormat(glu::mapGLCompressedTexFormat(m_texFormat)) &&
        !m_context.getContextInfo().isExtensionSupported("GL_KHR_texture_compression_astc_ldr"))
    {
        throw tcu::NotSupportedError("Test requires GL_KHR_texture_compression_astc_ldr extension");
    }

    if (m_texFormat == GL_BGRA && !m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_format_BGRA8888"))
        throw tcu::NotSupportedError("Test requires GL_EXT_texture_format_BGRA8888 extension");

    if (m_context.getRenderTarget().getWidth() < VIEWPORT_WIDTH ||
        m_context.getRenderTarget().getHeight() < VIEWPORT_HEIGHT)
    {
        throw tcu::NotSupportedError("Test requires " + de::toString<int>(VIEWPORT_WIDTH) + "x" +
                                     de::toString<int>(VIEWPORT_HEIGHT) + " viewport");
    }

    // resources

    m_texture = genDefaultTexture<glu::Texture2D>(m_context.getRenderContext(), m_context.getContextInfo(), m_texFormat,
                                                  tcu::IVec2(m_texWidth, m_texHeight));

    m_testCtx.getLog() << tcu::TestLog::Message << "Created texture with format "
                       << glu::getTextureFormatName(m_texFormat) << ", size (" << m_texture->getRefTexture().getWidth()
                       << ", " << m_texture->getRefTexture().getHeight() << ")\n"
                       << "Setting sampling state using "
                       << ((m_stateType == STATE_TEXTURE_PARAM) ? ("texture state") : ("sampler state"))
                       << tcu::TestLog::EndMessage;

    if (m_samplingFunction == SAMPLE_FILTER)
    {
        const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

        m_renderer = de::MovePtr<gls::TextureTestUtil::TextureRenderer>(new gls::TextureTestUtil::TextureRenderer(
            m_context.getRenderContext(), m_testCtx.getLog(), glslVersion, glu::PRECISION_HIGHP));
    }
    else
    {
        m_gatherProgram = de::MovePtr<glu::ShaderProgram>(genGatherProgram());

        m_testCtx.getLog() << tcu::TestLog::Message << "Using texture gather to sample texture"
                           << tcu::TestLog::EndMessage << *m_gatherProgram;

        if (!m_gatherProgram->isOk())
            throw tcu::TestError("failed to build program");
    }
}

void TextureBorderClampTest::deinit(void)
{
    m_texture.clear();
    m_renderer.clear();
    m_gatherProgram.clear();
}

TextureBorderClampTest::IterateResult TextureBorderClampTest::iterate(void)
{
    const IterationConfig iterationConfig = getIteration(m_iterationNdx);
    const std::string iterationDesc =
        "Iteration " + de::toString(m_iterationNdx + 1) +
        (iterationConfig.description.empty() ? ("") : (" - " + iterationConfig.description));
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration", iterationDesc);
    tcu::Surface renderedFrame(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
    const glu::TextureTestUtil::ReferenceParams samplerParams = genSamplerParams(iterationConfig);

    logParams(iterationConfig, samplerParams);
    renderTo(renderedFrame, iterationConfig, samplerParams);
    verifyImage(renderedFrame, iterationConfig, samplerParams);

    if (++m_iterationNdx == getNumIterations())
    {
        m_result.setTestContextResult(m_testCtx);
        return STOP;
    }
    return CONTINUE;
}

void TextureBorderClampTest::logParams(const IterationConfig &config,
                                       const glu::TextureTestUtil::ReferenceParams &samplerParams)
{
    const std::string borderColorString = (m_channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER) ?
                                              (de::toString(config.borderColor.get<int32_t>())) :
                                          (m_channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER) ?
                                              (de::toString(config.borderColor.get<uint32_t>())) :
                                              (de::toString(config.borderColor.get<float>()));

    m_testCtx.getLog() << tcu::TestLog::Message << "Rendering full screen quad, tex coords bottom-left: " << config.p0
                       << ", top-right " << config.p1 << "\n"
                       << "Border color is " << borderColorString << "\n"
                       << "Texture lookup bias: " << samplerParams.colorBias << "\n"
                       << "Texture lookup scale: " << samplerParams.colorScale << "\n"
                       << "Filters: min = "
                       << glu::getTextureFilterName(glu::getGLFilterMode(samplerParams.sampler.minFilter))
                       << ", mag = " << glu::getTextureFilterName(glu::getGLFilterMode(samplerParams.sampler.magFilter))
                       << "\n"
                       << "Wrap mode: s = " << glu::getRepeatModeStr(config.sWrapMode)
                       << ", t = " << glu::getRepeatModeStr(config.tWrapMode) << "\n"
                       << tcu::TestLog::EndMessage;

    if (m_sampleMode == tcu::Sampler::MODE_DEPTH)
        m_testCtx.getLog() << tcu::TestLog::Message << "Depth stencil texture mode is DEPTH_COMPONENT"
                           << tcu::TestLog::EndMessage;
    else if (m_sampleMode == tcu::Sampler::MODE_STENCIL)
        m_testCtx.getLog() << tcu::TestLog::Message << "Depth stencil texture mode is STENCIL_INDEX"
                           << tcu::TestLog::EndMessage;

    if (config.compareMode != GL_NONE)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Texture mode is COMPARE_REF_TO_TEXTURE, mode = "
                           << glu::getCompareFuncStr(config.compareMode) << "\n"
                           << "Compare reference value = " << config.compareRef << "\n"
                           << tcu::TestLog::EndMessage;
    }
}

void TextureBorderClampTest::renderTo(tcu::Surface &surface, const IterationConfig &config,
                                      const glu::TextureTestUtil::ReferenceParams &samplerParams)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const gls::TextureTestUtil::RandomViewport viewport(m_context.getRenderTarget(), VIEWPORT_WIDTH, VIEWPORT_HEIGHT,
                                                        getIterationSeed(config));
    std::vector<float> texCoord;
    de::MovePtr<glu::Sampler> sampler;

    glu::TextureTestUtil::computeQuadTexCoord2D(texCoord, config.p0, config.p1);

    // Bind to unit 0.
    gl.activeTexture(GL_TEXTURE0);
    gl.bindTexture(GL_TEXTURE_2D, m_texture->getGLTexture());

    if (m_sampleMode == tcu::Sampler::MODE_DEPTH)
        gl.texParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
    else if (m_sampleMode == tcu::Sampler::MODE_STENCIL)
        gl.texParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);

    if (config.compareMode == GL_NONE)
    {
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_ALWAYS);
    }
    else
    {
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, config.compareMode);
    }

    if (m_stateType == STATE_TEXTURE_PARAM)
    {
        // Setup filtering and wrap modes.
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glu::getGLWrapMode(samplerParams.sampler.wrapS));
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glu::getGLWrapMode(samplerParams.sampler.wrapT));
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(samplerParams.sampler.minFilter));
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(samplerParams.sampler.magFilter));

        switch (m_channelClass)
        {
        case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
        case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
        case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
            gl.texParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, config.borderColor.getAccess<float>());
            break;

        case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
            gl.texParameterIiv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, config.borderColor.getAccess<int32_t>());
            break;

        case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
            gl.texParameterIuiv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, config.borderColor.getAccess<uint32_t>());
            break;

        default:
            DE_ASSERT(false);
        }
    }
    else if (m_stateType == STATE_SAMPLER_PARAM)
    {
        const tcu::Vec4 blue(0.0f, 0.0f, 1.0f, 1.0f);

        // Setup filtering and wrap modes to bad values
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        gl.texParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, blue.getPtr()); // just set some unlikely color

        // setup sampler to correct values
        sampler = de::MovePtr<glu::Sampler>(new glu::Sampler(m_context.getRenderContext()));

        gl.samplerParameteri(**sampler, GL_TEXTURE_WRAP_S, glu::getGLWrapMode(samplerParams.sampler.wrapS));
        gl.samplerParameteri(**sampler, GL_TEXTURE_WRAP_T, glu::getGLWrapMode(samplerParams.sampler.wrapT));
        gl.samplerParameteri(**sampler, GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(samplerParams.sampler.minFilter));
        gl.samplerParameteri(**sampler, GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(samplerParams.sampler.magFilter));

        switch (m_channelClass)
        {
        case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
        case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
        case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
            gl.samplerParameterfv(**sampler, GL_TEXTURE_BORDER_COLOR, config.borderColor.getAccess<float>());
            break;

        case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
            gl.samplerParameterIiv(**sampler, GL_TEXTURE_BORDER_COLOR, config.borderColor.getAccess<int32_t>());
            break;

        case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
            gl.samplerParameterIuiv(**sampler, GL_TEXTURE_BORDER_COLOR, config.borderColor.getAccess<uint32_t>());
            break;

        default:
            DE_ASSERT(false);
        }

        gl.bindSampler(0, **sampler);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");

    gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
    renderQuad(&texCoord[0], samplerParams);
    glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, surface.getAccess());
}

void TextureBorderClampTest::renderQuad(const float *texCoord,
                                        const glu::TextureTestUtil::ReferenceParams &samplerParams)
{
    // use TextureRenderer for basic rendering, use custom for gather
    if (m_samplingFunction == SAMPLE_FILTER)
        m_renderer->renderQuad(0, texCoord, samplerParams);
    else
    {
        static const float position[]                = {-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, +1.0f, 0.0f, 1.0f,
                                                        +1.0f, -1.0f, 0.0f, 1.0f, +1.0f, +1.0f, 0.0f, 1.0f};
        static const uint16_t indices[]              = {0, 1, 2, 2, 1, 3};
        const glu::VertexArrayBinding vertexArrays[] = {glu::va::Float("a_position", 4, 4, 0, &position[0]),
                                                        glu::va::Float("a_texcoord", 2, 4, 0, texCoord)};

        const glw::Functions &gl = m_context.getRenderContext().getFunctions();
        const uint32_t progId    = m_gatherProgram->getProgram();

        gl.useProgram(progId);
        gl.uniform1i(gl.getUniformLocation(progId, "u_sampler"), 0);
        if (m_useShadowSampler)
            gl.uniform1f(gl.getUniformLocation(progId, "u_ref"), samplerParams.ref);
        gl.uniform4fv(gl.getUniformLocation(progId, "u_colorScale"), 1, samplerParams.colorScale.getPtr());
        gl.uniform4fv(gl.getUniformLocation(progId, "u_colorBias"), 1, samplerParams.colorBias.getPtr());

        glu::draw(m_context.getRenderContext(), progId, DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
                  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
    }
}

void TextureBorderClampTest::verifyImage(const tcu::Surface &renderedFrame, const IterationConfig &config,
                                         const glu::TextureTestUtil::ReferenceParams &samplerParams)
{
    const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();

    tcu::LodPrecision lodPrecision;
    std::vector<float> texCoord;
    bool verificationOk;

    glu::TextureTestUtil::computeQuadTexCoord2D(texCoord, config.p0, config.p1);

    lodPrecision.derivateBits = 18;
    lodPrecision.lodBits      = 5;

    if (samplerParams.sampler.compare == tcu::Sampler::COMPAREMODE_NONE)
    {
        const tcu::TextureFormat texFormat =
            tcu::getEffectiveDepthStencilTextureFormat(m_texture->getRefTexture().getFormat(), m_sampleMode);
        const bool isNearestMinFilter = samplerParams.sampler.minFilter == tcu::Sampler::NEAREST ||
                                        samplerParams.sampler.minFilter == tcu::Sampler::NEAREST_MIPMAP_NEAREST;
        const bool isNearestMagFilter = samplerParams.sampler.magFilter == tcu::Sampler::NEAREST;
        const bool isNearestOnly      = isNearestMinFilter && isNearestMagFilter;
        const bool isSRGB = texFormat.order == tcu::TextureFormat::sRGB || texFormat.order == tcu::TextureFormat::sRGBA;
        const int colorErrorBits = (isNearestOnly && !isSRGB) ? (1) : (2);
        const tcu::IVec4 colorBits =
            tcu::max(glu::TextureTestUtil::getBitsVec(pixelFormat) - tcu::IVec4(colorErrorBits), tcu::IVec4(0));
        tcu::LookupPrecision lookupPrecision;

        lookupPrecision.colorThreshold = tcu::computeFixedPointThreshold(colorBits) / samplerParams.colorScale;
        lookupPrecision.coordBits      = tcu::IVec3(20, 20, 0);
        lookupPrecision.uvwBits        = tcu::IVec3(5, 5, 0);
        lookupPrecision.colorMask      = glu::TextureTestUtil::getCompareMask(pixelFormat);

        if (m_samplingFunction == SAMPLE_FILTER)
        {
            verificationOk = verifyTextureSampleResult(renderedFrame.getAccess(), &texCoord[0], samplerParams,
                                                       lodPrecision, lookupPrecision);
        }
        else if (m_samplingFunction == SAMPLE_GATHER)
        {
            verificationOk =
                verifyTextureGatherResult(renderedFrame.getAccess(), &texCoord[0], samplerParams, lookupPrecision);
        }
        else
        {
            DE_ASSERT(false);
            verificationOk = false;
        }
    }
    else
    {
        tcu::TexComparePrecision texComparePrecision;
        tcu::TexComparePrecision lowQualityTexComparePrecision;
        tcu::LodPrecision lowQualityLodPrecision = lodPrecision;

        texComparePrecision.coordBits     = tcu::IVec3(20, 20, 0);
        texComparePrecision.uvwBits       = tcu::IVec3(7, 7, 0);
        texComparePrecision.pcfBits       = 5;
        texComparePrecision.referenceBits = 16;
        texComparePrecision.resultBits    = de::max(0, pixelFormat.redBits - 1);

        lowQualityTexComparePrecision.coordBits     = tcu::IVec3(20, 20, 0);
        lowQualityTexComparePrecision.uvwBits       = tcu::IVec3(4, 4, 0);
        lowQualityTexComparePrecision.pcfBits       = 0;
        lowQualityTexComparePrecision.referenceBits = 16;
        lowQualityTexComparePrecision.resultBits    = de::max(0, pixelFormat.redBits - 1);

        lowQualityLodPrecision.lodBits = 4;

        if (m_samplingFunction == SAMPLE_FILTER)
        {
            verificationOk =
                verifyTextureCompareResult(renderedFrame.getAccess(), &texCoord[0], samplerParams, texComparePrecision,
                                           lowQualityTexComparePrecision, lodPrecision, lowQualityLodPrecision);
        }
        else if (m_samplingFunction == SAMPLE_GATHER)
        {
            verificationOk = verifyTextureGatherCmpResult(renderedFrame.getAccess(), &texCoord[0], samplerParams,
                                                          texComparePrecision, lowQualityTexComparePrecision);
        }
        else
        {
            DE_ASSERT(false);
            verificationOk = false;
        }
    }

    if (!verificationOk)
        m_result.fail("Image verification failed");
}

bool TextureBorderClampTest::verifyTextureSampleResult(const tcu::ConstPixelBufferAccess &renderedFrame,
                                                       const float *texCoord,
                                                       const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                                       const tcu::LodPrecision &lodPrecision,
                                                       const tcu::LookupPrecision &lookupPrecision)
{
    const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();
    tcu::Surface reference(renderedFrame.getWidth(), renderedFrame.getHeight());
    tcu::Surface errorMask(renderedFrame.getWidth(), renderedFrame.getHeight());
    int numFailedPixels;

    glu::TextureTestUtil::sampleTexture(tcu::SurfaceAccess(reference, pixelFormat), m_texture->getRefTexture(),
                                        texCoord, samplerParams);

    numFailedPixels = glu::TextureTestUtil::computeTextureLookupDiff(
        renderedFrame, reference.getAccess(), errorMask.getAccess(), m_texture->getRefTexture(), texCoord,
        samplerParams, lookupPrecision, lodPrecision, m_testCtx.getWatchDog());

    if (numFailedPixels > 0)
        m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels
                           << " invalid pixels!" << tcu::TestLog::EndMessage;
    m_testCtx.getLog() << tcu::TestLog::ImageSet("VerifyResult", "Verification result")
                       << tcu::TestLog::Image("Rendered", "Rendered image", renderedFrame);
    if (numFailedPixels > 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Image("Reference", "Ideal reference image", reference)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask);
    }
    m_testCtx.getLog() << tcu::TestLog::EndImageSet;

    return (numFailedPixels == 0);
}

bool TextureBorderClampTest::verifyTextureCompareResult(const tcu::ConstPixelBufferAccess &renderedFrame,
                                                        const float *texCoord,
                                                        const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                                        const tcu::TexComparePrecision &texComparePrecision,
                                                        const tcu::TexComparePrecision &lowQualityTexComparePrecision,
                                                        const tcu::LodPrecision &lodPrecision,
                                                        const tcu::LodPrecision &lowQualityLodPrecision)
{
    const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();
    const int colorErrorBits           = 1;
    const tcu::IVec4 nonShadowBits =
        tcu::max(glu::TextureTestUtil::getBitsVec(pixelFormat) - tcu::IVec4(colorErrorBits), tcu::IVec4(0));
    const tcu::Vec3 nonShadowThreshold = tcu::computeFixedPointThreshold(nonShadowBits).swizzle(1, 2, 3);
    std::vector<tcu::ConstPixelBufferAccess> srcLevelStorage;
    const tcu::Texture2DView effectiveView =
        tcu::getEffectiveTextureView(m_texture->getRefTexture(), srcLevelStorage, samplerParams.sampler);
    tcu::Surface reference(renderedFrame.getWidth(), renderedFrame.getHeight());
    tcu::Surface errorMask(renderedFrame.getWidth(), renderedFrame.getHeight());
    int numFailedPixels;

    glu::TextureTestUtil::sampleTexture(tcu::SurfaceAccess(reference, pixelFormat), effectiveView, texCoord,
                                        samplerParams);

    numFailedPixels = glu::TextureTestUtil::computeTextureCompareDiff(
        renderedFrame, reference.getAccess(), errorMask.getAccess(), effectiveView, texCoord, samplerParams,
        texComparePrecision, lodPrecision, nonShadowThreshold);

    if (numFailedPixels > 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Warning: Verification assuming high-quality PCF filtering failed."
                           << tcu::TestLog::EndMessage;

        numFailedPixels = glu::TextureTestUtil::computeTextureCompareDiff(
            renderedFrame, reference.getAccess(), errorMask.getAccess(), effectiveView, texCoord, samplerParams,
            lowQualityTexComparePrecision, lowQualityLodPrecision, nonShadowThreshold);

        if (numFailedPixels > 0)
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "ERROR: Verification against low precision requirements failed, failing test case."
                               << tcu::TestLog::EndMessage;
        else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            m_result.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality result");
    }

    if (numFailedPixels > 0)
        m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels
                           << " invalid pixels!" << tcu::TestLog::EndMessage;
    m_testCtx.getLog() << tcu::TestLog::ImageSet("VerifyResult", "Verification result")
                       << tcu::TestLog::Image("Rendered", "Rendered image", renderedFrame);
    if (numFailedPixels > 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Image("Reference", "Ideal reference image", reference)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask);
    }
    m_testCtx.getLog() << tcu::TestLog::EndImageSet;

    return (numFailedPixels == 0);
}

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);
}

bool TextureBorderClampTest::verifyTextureGatherResult(const tcu::ConstPixelBufferAccess &renderedFrame,
                                                       const float *texCoordArray,
                                                       const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                                       const tcu::LookupPrecision &lookupPrecision)
{
    const tcu::Vec2 texCoords[4] = {
        tcu::Vec2(texCoordArray[0], texCoordArray[1]),
        tcu::Vec2(texCoordArray[2], texCoordArray[3]),
        tcu::Vec2(texCoordArray[4], texCoordArray[5]),
        tcu::Vec2(texCoordArray[6], texCoordArray[7]),
    };

    const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();
    const uint8_t fbColormask          = tcu::getColorMask(pixelFormat);

    std::vector<tcu::ConstPixelBufferAccess> srcLevelStorage;
    const tcu::Texture2DView effectiveView =
        tcu::getEffectiveTextureView(m_texture->getRefTexture(), srcLevelStorage, samplerParams.sampler);

    tcu::Surface reference(renderedFrame.getWidth(), renderedFrame.getHeight());
    tcu::Surface errorMask(renderedFrame.getWidth(), renderedFrame.getHeight());
    int numFailedPixels = 0;

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

    for (int py = 0; py < reference.getHeight(); ++py)
        for (int px = 0; px < reference.getWidth(); ++px)
        {
            const tcu::Vec2 viewportCoord = (tcu::Vec2((float)px, (float)py) + tcu::Vec2(0.5f)) /
                                            tcu::Vec2((float)reference.getWidth(), (float)reference.getHeight());
            const tcu::Vec2 texCoord       = triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
            const tcu::Vec4 referenceValue = effectiveView.gatherOffsets(
                samplerParams.sampler, texCoord.x(), texCoord.y(), 0, glu::getDefaultGatherOffsets());
            const tcu::Vec4 referencePixel = referenceValue * samplerParams.colorScale + samplerParams.colorBias;
            const tcu::Vec4 resultPixel    = renderedFrame.getPixel(px, py);
            const tcu::Vec4 resultValue    = (resultPixel - samplerParams.colorBias) / samplerParams.colorScale;

            reference.setPixel(px, py, tcu::toRGBAMasked(referenceValue, fbColormask));

            if (tcu::boolAny(tcu::logicalAnd(
                    lookupPrecision.colorMask,
                    tcu::greaterThan(tcu::absDiff(resultPixel, referencePixel), lookupPrecision.colorThreshold))))
            {
                if (!tcu::isGatherOffsetsResultValid(effectiveView, samplerParams.sampler, lookupPrecision, texCoord, 0,
                                                     glu::getDefaultGatherOffsets(), resultValue))
                {
                    errorMask.setPixel(px, py, tcu::RGBA::red());
                    ++numFailedPixels;
                }
            }
        }

    if (numFailedPixels > 0)
        m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels
                           << " invalid pixels!" << tcu::TestLog::EndMessage;
    m_testCtx.getLog() << tcu::TestLog::ImageSet("VerifyResult", "Verification result")
                       << tcu::TestLog::Image("Rendered", "Rendered image", renderedFrame);
    if (numFailedPixels > 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Image("Reference", "Ideal reference image", reference)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask);
    }
    m_testCtx.getLog() << tcu::TestLog::EndImageSet;

    return (numFailedPixels == 0);
}

bool TextureBorderClampTest::verifyTextureGatherCmpResult(const tcu::ConstPixelBufferAccess &renderedFrame,
                                                          const float *texCoordArray,
                                                          const glu::TextureTestUtil::ReferenceParams &samplerParams,
                                                          const tcu::TexComparePrecision &texComparePrecision,
                                                          const tcu::TexComparePrecision &lowQualityTexComparePrecision)
{
    const tcu::Vec2 texCoords[4] = {
        tcu::Vec2(texCoordArray[0], texCoordArray[1]),
        tcu::Vec2(texCoordArray[2], texCoordArray[3]),
        tcu::Vec2(texCoordArray[4], texCoordArray[5]),
        tcu::Vec2(texCoordArray[6], texCoordArray[7]),
    };

    std::vector<tcu::ConstPixelBufferAccess> srcLevelStorage;
    const tcu::Texture2DView effectiveView =
        tcu::getEffectiveTextureView(m_texture->getRefTexture(), srcLevelStorage, samplerParams.sampler);

    const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();
    const tcu::BVec4 colorMask         = glu::TextureTestUtil::getCompareMask(pixelFormat);
    const uint8_t fbColormask          = tcu::getColorMask(pixelFormat);
    tcu::Surface reference(renderedFrame.getWidth(), renderedFrame.getHeight());
    tcu::Surface errorMask(renderedFrame.getWidth(), renderedFrame.getHeight());
    int numFailedPixels = 0;
    bool lowQuality     = false;

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

    for (int py = 0; py < reference.getHeight(); ++py)
        for (int px = 0; px < reference.getWidth(); ++px)
        {
            const tcu::Vec2 viewportCoord = (tcu::Vec2((float)px, (float)py) + tcu::Vec2(0.5f)) /
                                            tcu::Vec2((float)reference.getWidth(), (float)reference.getHeight());
            const tcu::Vec2 texCoord       = triQuadInterpolate(texCoords, viewportCoord.x(), viewportCoord.y());
            const float refZ               = samplerParams.ref;
            const tcu::Vec4 referenceValue = effectiveView.gatherOffsetsCompare(
                samplerParams.sampler, refZ, texCoord.x(), texCoord.y(), glu::getDefaultGatherOffsets());
            const tcu::Vec4 resultValue = renderedFrame.getPixel(px, py);

            reference.setPixel(px, py, tcu::toRGBAMasked(referenceValue, fbColormask));

            if (tcu::boolAny(tcu::logicalAnd(colorMask, tcu::notEqual(referenceValue, resultValue))))
            {
                if (!tcu::isGatherOffsetsCompareResultValid(effectiveView, samplerParams.sampler, texComparePrecision,
                                                            texCoord, glu::getDefaultGatherOffsets(), refZ,
                                                            resultValue))
                {
                    lowQuality = true;

                    // fall back to low quality verification
                    if (!tcu::isGatherOffsetsCompareResultValid(effectiveView, samplerParams.sampler,
                                                                lowQualityTexComparePrecision, texCoord,
                                                                glu::getDefaultGatherOffsets(), refZ, resultValue))
                    {
                        errorMask.setPixel(px, py, tcu::RGBA::red());
                        ++numFailedPixels;
                    }
                }
            }
        }

    if (numFailedPixels > 0)
        m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels
                           << " invalid pixels!" << tcu::TestLog::EndMessage;
    else if (lowQuality)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Warning: Verification assuming high-quality PCF filtering failed."
                           << tcu::TestLog::EndMessage;
        m_result.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality result");
    }

    m_testCtx.getLog() << tcu::TestLog::ImageSet("VerifyResult", "Verification result")
                       << tcu::TestLog::Image("Rendered", "Rendered image", renderedFrame);
    if (numFailedPixels > 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Image("Reference", "Ideal reference image", reference)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask);
    }
    m_testCtx.getLog() << tcu::TestLog::EndImageSet;

    return (numFailedPixels == 0);
}

const glu::Texture2D *TextureBorderClampTest::getTexture(void) const
{
    return m_texture.get();
}

uint32_t TextureBorderClampTest::getIterationSeed(const IterationConfig &config) const
{
    tcu::SeedBuilder builder;
    builder << std::string(getName()) << m_iterationNdx << m_texFormat << config.minFilter << config.magFilter
            << m_texture->getRefTexture().getWidth() << m_texture->getRefTexture().getHeight();
    return builder.get();
}

glu::TextureTestUtil::ReferenceParams TextureBorderClampTest::genSamplerParams(const IterationConfig &config) const
{
    const tcu::TextureFormat texFormat =
        tcu::getEffectiveDepthStencilTextureFormat(m_texture->getRefTexture().getFormat(), m_sampleMode);
    glu::TextureTestUtil::ReferenceParams refParams(glu::TextureTestUtil::TEXTURETYPE_2D);

    refParams.sampler = glu::mapGLSampler(config.sWrapMode, config.tWrapMode, config.minFilter, config.magFilter);
    refParams.sampler.borderColor = config.borderColor;
    refParams.sampler.compare =
        (!m_useShadowSampler) ? (tcu::Sampler::COMPAREMODE_NONE) : (glu::mapGLCompareFunc(config.compareMode));
    refParams.sampler.depthStencilMode = m_sampleMode;
    refParams.lodMode                  = glu::TextureTestUtil::LODMODE_EXACT;
    refParams.samplerType              = (!m_useShadowSampler) ? (glu::TextureTestUtil::getSamplerType(texFormat)) :
                                                                 (glu::TextureTestUtil::SAMPLERTYPE_SHADOW);
    refParams.colorScale               = config.lookupScale;
    refParams.colorBias                = config.lookupBias;
    refParams.ref                      = config.compareRef;

    // compare can only be used with depth textures
    if (!isDepthFormat(m_texFormat, m_sampleMode))
        DE_ASSERT(refParams.sampler.compare == tcu::Sampler::COMPAREMODE_NONE);

    // sampler type must match compare mode
    DE_ASSERT(m_useShadowSampler == (config.compareMode != GL_NONE));

    // in gather, weird mapping is most likely an error
    if (m_samplingFunction == SAMPLE_GATHER)
    {
        DE_ASSERT(refParams.colorScale == tcu::Vec4(refParams.colorScale.x()));
        DE_ASSERT(refParams.colorBias == tcu::Vec4(refParams.colorBias.x()));
    }

    return refParams;
}

glu::ShaderProgram *TextureBorderClampTest::genGatherProgram(void) const
{
    const std::string glslVersionDecl =
        glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType()));
    const std::string vtxSource = glslVersionDecl + "\n"
                                                    "in highp vec4 a_position;\n"
                                                    "in highp vec2 a_texcoord;\n"
                                                    "out highp vec2 v_texcoord;\n"
                                                    "void main()\n"
                                                    "{\n"
                                                    "    gl_Position = a_position;\n"
                                                    "    v_texcoord = a_texcoord;\n"
                                                    "}\n";
    const char *samplerType;
    const char *lookup;
    std::ostringstream fragSource;

    if (m_useShadowSampler)
    {
        samplerType = "sampler2DShadow";
        lookup      = "textureGather(u_sampler, v_texcoord, u_ref)";
    }
    else
    {
        switch (m_channelClass)
        {
        case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
        case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
        case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
            samplerType = "sampler2D";
            lookup      = "textureGather(u_sampler, v_texcoord)";
            break;

        case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
            samplerType = "isampler2D";
            lookup      = "vec4(textureGather(u_sampler, v_texcoord))";
            break;

        case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
            samplerType = "usampler2D";
            lookup      = "vec4(textureGather(u_sampler, v_texcoord))";
            break;

        default:
            samplerType = "";
            lookup      = "";
            DE_ASSERT(false);
        }
    }

    fragSource << glslVersionDecl + "\n"
                                    "uniform highp "
               << samplerType
               << " u_sampler;\n"
                  "uniform highp vec4 u_colorScale;\n"
                  "uniform highp vec4 u_colorBias;\n"
               << ((m_useShadowSampler) ? ("uniform highp float u_ref;\n") : (""))
               << "in highp vec2 v_texcoord;\n"
                  "layout(location=0) out highp vec4 o_color;\n"
                  "void main()\n"
                  "{\n"
                  "    o_color = "
               << lookup
               << " * u_colorScale + u_colorBias;\n"
                  "}\n";

    return new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
                                                                    << glu::VertexSource(vtxSource)
                                                                    << glu::FragmentSource(fragSource.str()));
}

class TextureBorderClampFormatCase : public TextureBorderClampTest
{
public:
    TextureBorderClampFormatCase(Context &context, const char *name, const char *description, uint32_t texFormat,
                                 tcu::Sampler::DepthStencilMode mode, StateType stateType, SizeType sizeType,
                                 uint32_t filter, SamplingFunction samplingFunction);

private:
    void init(void);

    int getNumIterations(void) const;
    IterationConfig getIteration(int ndx) const;

    const SizeType m_sizeType;

    std::vector<IterationConfig> m_iterations;
};

TextureBorderClampFormatCase::TextureBorderClampFormatCase(Context &context, const char *name, const char *description,
                                                           uint32_t texFormat, tcu::Sampler::DepthStencilMode mode,
                                                           StateType stateType, SizeType sizeType, uint32_t filter,
                                                           SamplingFunction samplingFunction)
    : TextureBorderClampTest(context, name, description, texFormat, mode, stateType,
                             (sizeType == SIZE_POT) ? (32) : (17), (sizeType == SIZE_POT) ? (16) : (31),
                             samplingFunction, filter, FLAG_TEST_FLOAT_FILTERABLE)
    , m_sizeType(sizeType)
{
    if (m_sizeType == SIZE_POT)
        DE_ASSERT(deIsPowerOfTwo32(m_texWidth) && deIsPowerOfTwo32(m_texHeight));
    else
        DE_ASSERT(!deIsPowerOfTwo32(m_texWidth) && !deIsPowerOfTwo32(m_texHeight));

    if (glu::isCompressedFormat(texFormat))
    {
        const tcu::CompressedTexFormat compressedFormat = glu::mapGLCompressedTexFormat(texFormat);
        const tcu::IVec3 blockPixelSize                 = tcu::getBlockPixelSize(compressedFormat);

        // is (not) multiple of a block size
        if (m_sizeType == SIZE_POT)
            DE_ASSERT((m_texWidth % blockPixelSize.x()) == 0 && (m_texHeight % blockPixelSize.y()) == 0);
        else
            DE_ASSERT((m_texWidth % blockPixelSize.x()) != 0 && (m_texHeight % blockPixelSize.y()) != 0);

        DE_UNREF(blockPixelSize);
    }
}

void TextureBorderClampFormatCase::init(void)
{
    TextureBorderClampTest::init();

    // \note TextureBorderClampTest::init() creates texture
    const tcu::TextureFormat texFormat =
        tcu::getEffectiveDepthStencilTextureFormat(getTexture()->getRefTexture().getFormat(), m_sampleMode);
    const tcu::TextureFormatInfo texFormatInfo = tcu::getTextureFormatInfo(texFormat);

    // iterations

    {
        IterationConfig iteration;
        iteration.p0          = tcu::Vec2(-1.5f, -3.0f);
        iteration.p1          = tcu::Vec2(1.5f, 2.5f);
        iteration.borderColor = mapToFormatColorRepresentable(texFormat, tcu::Vec4(0.3f, 0.7f, 0.2f, 0.5f));
        m_iterations.push_back(iteration);
    }
    {
        IterationConfig iteration;
        iteration.p0          = tcu::Vec2(-0.5f, 0.75f);
        iteration.p1          = tcu::Vec2(0.25f, 1.25f);
        iteration.borderColor = mapToFormatColorRepresentable(texFormat, tcu::Vec4(0.9f, 0.2f, 0.4f, 0.6f));
        m_iterations.push_back(iteration);
    }

    // common parameters
    for (int ndx = 0; ndx < (int)m_iterations.size(); ++ndx)
    {
        IterationConfig &iteration = m_iterations[ndx];

        if (m_samplingFunction == SAMPLE_GATHER)
        {
            iteration.lookupScale = tcu::Vec4(texFormatInfo.lookupScale.x());
            iteration.lookupBias  = tcu::Vec4(texFormatInfo.lookupBias.x());
        }
        else
        {
            iteration.lookupScale = texFormatInfo.lookupScale;
            iteration.lookupBias  = texFormatInfo.lookupBias;
        }

        iteration.minFilter   = m_filter;
        iteration.magFilter   = m_filter;
        iteration.sWrapMode   = GL_CLAMP_TO_BORDER;
        iteration.tWrapMode   = GL_CLAMP_TO_BORDER;
        iteration.compareMode = GL_NONE;
        iteration.compareRef  = 0.0f;
    }
}

int TextureBorderClampFormatCase::getNumIterations(void) const
{
    return (int)m_iterations.size();
}

TextureBorderClampTest::IterationConfig TextureBorderClampFormatCase::getIteration(int ndx) const
{
    return m_iterations[ndx];
}

class TextureBorderClampRangeClampCase : public TextureBorderClampTest
{
public:
    TextureBorderClampRangeClampCase(Context &context, const char *name, const char *description, uint32_t texFormat,
                                     tcu::Sampler::DepthStencilMode mode, uint32_t filter);

private:
    void init(void);

    int getNumIterations(void) const;
    IterationConfig getIteration(int ndx) const;

    std::vector<IterationConfig> m_iterations;
};

TextureBorderClampRangeClampCase::TextureBorderClampRangeClampCase(Context &context, const char *name,
                                                                   const char *description, uint32_t texFormat,
                                                                   tcu::Sampler::DepthStencilMode mode, uint32_t filter)
    : TextureBorderClampTest(context, name, description, texFormat, mode, TextureBorderClampTest::STATE_TEXTURE_PARAM,
                             8, 32, SAMPLE_FILTER, filter, FLAG_TEST_FLOAT_FILTERABLE)
{
}

void TextureBorderClampRangeClampCase::init(void)
{
    TextureBorderClampTest::init();

    const tcu::TextureFormat texFormat =
        tcu::getEffectiveDepthStencilTextureFormat(getTexture()->getRefTexture().getFormat(), m_sampleMode);
    const bool isDepth = isDepthFormat(m_texFormat, m_sampleMode);
    const bool isFloat = m_channelClass == tcu::TEXTURECHANNELCLASS_FLOATING_POINT;
    const bool isFixed = m_channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT ||
                         m_channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;
    const bool isPureInteger = m_channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER ||
                               m_channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER;

    if (isDepth || isFloat)
    {
        // infinities are commonly used values on depth/float borders
        {
            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-1.2f, -3.0f);
            iteration.p1          = tcu::Vec2(1.2f, 2.5f);
            iteration.borderColor = rr::GenericVec4(tcu::Vec4(std::numeric_limits<float>::infinity()));
            iteration.lookupScale =
                tcu::Vec4(0.5f); // scale & bias to [0.25, 0.5] range to make out-of-range values visible
            iteration.lookupBias  = tcu::Vec4(0.25f);
            iteration.description = "border value infinity";
            m_iterations.push_back(iteration);
        }
        {
            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-0.25f, -0.75f);
            iteration.p1          = tcu::Vec2(2.25f, 1.25f);
            iteration.borderColor = rr::GenericVec4(tcu::Vec4(-std::numeric_limits<float>::infinity()));
            iteration.lookupScale = tcu::Vec4(0.5f);
            iteration.lookupBias  = tcu::Vec4(0.25f);
            iteration.description = "border value negative infinity";
            m_iterations.push_back(iteration);
        }
    }
    else if (isPureInteger)
    {
        const tcu::IVec4 numBits = tcu::getTextureFormatBitDepth(texFormat);
        const bool isSigned      = m_channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER;

        // can't overflow 32bit integers with 32bit integers
        for (int ndx = 0; ndx < 4; ++ndx)
            DE_ASSERT(numBits[ndx] == 0 || numBits[ndx] == 8 || numBits[ndx] == 16);

        const tcu::IVec4 minValue   = getNBitIntegerVec4MinValue(isSigned, numBits);
        const tcu::IVec4 maxValue   = getNBitIntegerVec4MaxValue(isSigned, numBits);
        const tcu::IVec4 valueRange = maxValue - minValue;
        const tcu::IVec4 divSafeRange(
            (valueRange[0] == 0) ? (1) : (valueRange[0]), (valueRange[1] == 0) ? (1) : (valueRange[1]),
            (valueRange[2] == 0) ? (1) : (valueRange[2]), (valueRange[3] == 0) ? (1) : (valueRange[3]));

        // format max
        {
            const tcu::IVec4 value = maxValue + tcu::IVec4(1);

            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-1.2f, -3.0f);
            iteration.p1          = tcu::Vec2(1.2f, 2.5f);
            iteration.borderColor = (isSigned) ? (rr::GenericVec4(value)) : (rr::GenericVec4(value.cast<uint32_t>()));
            iteration.lookupScale = tcu::Vec4(0.5f) / divSafeRange.cast<float>();
            iteration.lookupBias  = (isSigned) ? (tcu::Vec4(0.5f)) : (tcu::Vec4(0.25f));
            iteration.description = "border values one larger than maximum";
            m_iterations.push_back(iteration);
        }
        // format min
        if (isSigned)
        {
            const tcu::IVec4 value = minValue - tcu::IVec4(1);

            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-0.25f, -0.75f);
            iteration.p1          = tcu::Vec2(2.25f, 1.25f);
            iteration.borderColor = rr::GenericVec4(value);
            iteration.lookupScale = tcu::Vec4(0.5f) / divSafeRange.cast<float>();
            iteration.lookupBias  = tcu::Vec4(0.5f);
            iteration.description = "border values one less than minimum";
            m_iterations.push_back(iteration);
        }
        // (u)int32 max
        {
            const tcu::IVec4 value = (isSigned) ? (tcu::IVec4(std::numeric_limits<int32_t>::max())) :
                                                  (tcu::IVec4(std::numeric_limits<uint32_t>::max()));

            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-1.6f, -2.1f);
            iteration.p1          = tcu::Vec2(1.2f, 3.5f);
            iteration.borderColor = (isSigned) ? (rr::GenericVec4(value)) : (rr::GenericVec4(value.cast<uint32_t>()));
            iteration.lookupScale = tcu::Vec4(0.5f) / divSafeRange.cast<float>();
            iteration.lookupBias  = tcu::Vec4(0.25f);
            iteration.description = "border values 32-bit maximum";
            m_iterations.push_back(iteration);
        }
        // int32 min
        if (isSigned)
        {
            const tcu::IVec4 value = tcu::IVec4(std::numeric_limits<int32_t>::min());

            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-2.6f, -4.0f);
            iteration.p1          = tcu::Vec2(1.1f, 1.5f);
            iteration.borderColor = rr::GenericVec4(value);
            iteration.lookupScale = tcu::Vec4(0.5f) / divSafeRange.cast<float>();
            iteration.lookupBias  = tcu::Vec4(0.25f);
            iteration.description = "border values 0";
            m_iterations.push_back(iteration);
        }
    }
    else if (isFixed)
    {
        const bool isSigned = m_channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT;
        const tcu::Vec4 lookupBias =
            (isSigned) ? (tcu::Vec4(0.5f)) :
                         (tcu::Vec4(0.25f)); // scale & bias to [0.25, 0.5] range to make out-of-range values visible
        const tcu::Vec4 lookupScale = (isSigned) ? (tcu::Vec4(0.25f)) : (tcu::Vec4(0.5f));

        {
            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-1.2f, -3.0f);
            iteration.p1          = tcu::Vec2(1.2f, 2.5f);
            iteration.borderColor = mapToFormatColorUnits(texFormat, tcu::Vec4(1.1f, 1.3f, 2.2f, 1.3f));
            iteration.lookupScale = lookupScale;
            iteration.lookupBias  = lookupBias;
            iteration.description = "border values larger than maximum";
            m_iterations.push_back(iteration);
        }
        {
            IterationConfig iteration;
            iteration.p0          = tcu::Vec2(-0.25f, -0.75f);
            iteration.p1          = tcu::Vec2(2.25f, 1.25f);
            iteration.borderColor = mapToFormatColorUnits(texFormat, tcu::Vec4(-0.2f, -0.9f, -2.4f, -0.6f));
            iteration.lookupScale = lookupScale;
            iteration.lookupBias  = lookupBias;
            iteration.description = "border values less than minimum";
            m_iterations.push_back(iteration);
        }
    }
    else
        DE_ASSERT(false);

    // common parameters
    for (int ndx = 0; ndx < (int)m_iterations.size(); ++ndx)
    {
        IterationConfig &iteration = m_iterations[ndx];

        iteration.minFilter   = m_filter;
        iteration.magFilter   = m_filter;
        iteration.sWrapMode   = GL_CLAMP_TO_BORDER;
        iteration.tWrapMode   = GL_CLAMP_TO_BORDER;
        iteration.compareMode = GL_NONE;
        iteration.compareRef  = 0.0f;
    }
}

int TextureBorderClampRangeClampCase::getNumIterations(void) const
{
    return (int)m_iterations.size();
}

TextureBorderClampTest::IterationConfig TextureBorderClampRangeClampCase::getIteration(int ndx) const
{
    return m_iterations[ndx];
}

class TextureBorderClampPerAxisCase2D : public TextureBorderClampTest
{
public:
    TextureBorderClampPerAxisCase2D(Context &context, const char *name, const char *description, uint32_t texFormat,
                                    tcu::Sampler::DepthStencilMode mode, SizeType sizeType, uint32_t filter,
                                    uint32_t texSWrap, uint32_t texTWrap, SamplingFunction samplingFunction);

private:
    void init(void);

    int getNumIterations(void) const;
    IterationConfig getIteration(int ndx) const;

    const uint32_t m_texSWrap;
    const uint32_t m_texTWrap;

    std::vector<IterationConfig> m_iterations;
};

TextureBorderClampPerAxisCase2D::TextureBorderClampPerAxisCase2D(Context &context, const char *name,
                                                                 const char *description, uint32_t texFormat,
                                                                 tcu::Sampler::DepthStencilMode mode, SizeType sizeType,
                                                                 uint32_t filter, uint32_t texSWrap, uint32_t texTWrap,
                                                                 SamplingFunction samplingFunction)
    : TextureBorderClampTest(context, name, description, texFormat, mode, TextureBorderClampTest::STATE_TEXTURE_PARAM,
                             (sizeType == SIZE_POT) ? (16) : (7), (sizeType == SIZE_POT) ? (8) : (9), samplingFunction,
                             filter, FLAG_TEST_FLOAT_FILTERABLE)
    , m_texSWrap(texSWrap)
    , m_texTWrap(texTWrap)
{
}

void TextureBorderClampPerAxisCase2D::init(void)
{
    TextureBorderClampTest::init();

    // \note TextureBorderClampTest::init() creates texture
    const tcu::TextureFormat texFormat =
        tcu::getEffectiveDepthStencilTextureFormat(getTexture()->getRefTexture().getFormat(), m_sampleMode);
    const tcu::TextureFormatInfo texFormatInfo = tcu::getTextureFormatInfo(texFormat);

    IterationConfig iteration;
    iteration.p0          = tcu::Vec2(-0.25f, -0.75f);
    iteration.p1          = tcu::Vec2(2.25f, 1.25f);
    iteration.borderColor = mapToFormatColorRepresentable(texFormat, tcu::Vec4(0.4f, 0.9f, 0.1f, 0.2f));

    if (m_samplingFunction == SAMPLE_GATHER)
    {
        iteration.lookupScale = tcu::Vec4(texFormatInfo.lookupScale.x());
        iteration.lookupBias  = tcu::Vec4(texFormatInfo.lookupBias.x());
    }
    else
    {
        iteration.lookupScale = texFormatInfo.lookupScale;
        iteration.lookupBias  = texFormatInfo.lookupBias;
    }

    iteration.minFilter   = m_filter;
    iteration.magFilter   = m_filter;
    iteration.sWrapMode   = m_texSWrap;
    iteration.tWrapMode   = m_texTWrap;
    iteration.compareMode = GL_NONE;
    iteration.compareRef  = 0.0f;

    m_iterations.push_back(iteration);
}

int TextureBorderClampPerAxisCase2D::getNumIterations(void) const
{
    return (int)m_iterations.size();
}

TextureBorderClampTest::IterationConfig TextureBorderClampPerAxisCase2D::getIteration(int ndx) const
{
    return m_iterations[ndx];
}

class TextureBorderClampDepthCompareCase : public TextureBorderClampTest
{
public:
    TextureBorderClampDepthCompareCase(Context &context, const char *name, const char *description, uint32_t texFormat,
                                       SizeType sizeType, uint32_t filter, SamplingFunction samplingFunction);

private:
    void init(void);

    int getNumIterations(void) const;
    IterationConfig getIteration(int ndx) const;

    std::vector<IterationConfig> m_iterations;
};

TextureBorderClampDepthCompareCase::TextureBorderClampDepthCompareCase(Context &context, const char *name,
                                                                       const char *description, uint32_t texFormat,
                                                                       SizeType sizeType, uint32_t filter,
                                                                       SamplingFunction samplingFunction)
    : TextureBorderClampTest(context, name, description, texFormat, tcu::Sampler::MODE_DEPTH,
                             TextureBorderClampTest::STATE_TEXTURE_PARAM, (sizeType == SIZE_POT) ? (32) : (13),
                             (sizeType == SIZE_POT) ? (16) : (17), samplingFunction, filter, FLAG_USE_SHADOW_SAMPLER)
{
}

void TextureBorderClampDepthCompareCase::init(void)
{
    TextureBorderClampTest::init();

    // 0.5 <= 0.7
    {
        IterationConfig iteration;
        iteration.p0          = tcu::Vec2(-0.15f, -0.35f);
        iteration.p1          = tcu::Vec2(1.25f, 1.1f);
        iteration.borderColor = rr::GenericVec4(tcu::Vec4(0.7f, 0.0f, 0.0f, 0.0f));
        iteration.description = "Border color in [0, 1] range";
        iteration.compareMode = GL_LEQUAL;
        iteration.compareRef  = 0.5f;
        m_iterations.push_back(iteration);
    }

    // 1.5 <= 1.0
    {
        IterationConfig iteration;
        iteration.p0          = tcu::Vec2(-0.15f, -0.35f);
        iteration.p1          = tcu::Vec2(1.25f, 1.1f);
        iteration.borderColor = rr::GenericVec4(tcu::Vec4(1.5f, 0.0f, 0.0f, 0.0f));
        iteration.description = "Border color > 1, should be clamped";
        iteration.compareMode = GL_LEQUAL;
        iteration.compareRef  = 1.0f;
        m_iterations.push_back(iteration);
    }

    // -0.5 >= 0.0
    {
        IterationConfig iteration;
        iteration.p0          = tcu::Vec2(-0.15f, -0.35f);
        iteration.p1          = tcu::Vec2(1.25f, 1.1f);
        iteration.borderColor = rr::GenericVec4(tcu::Vec4(-0.5f, 0.0f, 0.0f, 0.0f));
        iteration.description = "Border color < 0, should be clamped";
        iteration.compareMode = GL_GEQUAL;
        iteration.compareRef  = 0.0f;
        m_iterations.push_back(iteration);
    }

    // inf < 1.25
    {
        IterationConfig iteration;
        iteration.p0          = tcu::Vec2(-0.15f, -0.35f);
        iteration.p1          = tcu::Vec2(1.25f, 1.1f);
        iteration.borderColor = rr::GenericVec4(tcu::Vec4(std::numeric_limits<float>::infinity(), 0.0f, 0.0f, 0.0f));
        iteration.description = "Border color == inf, should be clamped; ref > 1";
        iteration.compareMode = GL_LESS;
        iteration.compareRef  = 1.25f;
        m_iterations.push_back(iteration);
    }

    // -inf > -0.5
    {
        IterationConfig iteration;
        iteration.p0          = tcu::Vec2(-0.15f, -0.35f);
        iteration.p1          = tcu::Vec2(1.25f, 1.1f);
        iteration.borderColor = rr::GenericVec4(tcu::Vec4(-std::numeric_limits<float>::infinity(), 0.0f, 0.0f, 0.0f));
        iteration.description = "Border color == inf, should be clamped; ref < 0";
        iteration.compareMode = GL_GREATER;
        iteration.compareRef  = -0.5f;
        m_iterations.push_back(iteration);
    }

    // common parameters
    for (int ndx = 0; ndx < (int)m_iterations.size(); ++ndx)
    {
        IterationConfig &iteration = m_iterations[ndx];

        iteration.lookupScale = tcu::Vec4(1.0);
        iteration.lookupBias  = tcu::Vec4(0.0);
        iteration.minFilter   = m_filter;
        iteration.magFilter   = m_filter;
        iteration.sWrapMode   = GL_CLAMP_TO_BORDER;
        iteration.tWrapMode   = GL_CLAMP_TO_BORDER;
    }
}

int TextureBorderClampDepthCompareCase::getNumIterations(void) const
{
    return (int)m_iterations.size();
}

TextureBorderClampTest::IterationConfig TextureBorderClampDepthCompareCase::getIteration(int ndx) const
{
    return m_iterations[ndx];
}

class TextureBorderClampUnusedChannelCase : public TextureBorderClampTest
{
public:
    TextureBorderClampUnusedChannelCase(Context &context, const char *name, const char *description, uint32_t texFormat,
                                        tcu::Sampler::DepthStencilMode depthStencilMode);

private:
    void init(void);

    int getNumIterations(void) const;
    IterationConfig getIteration(int ndx) const;

    std::vector<IterationConfig> m_iterations;
};

TextureBorderClampUnusedChannelCase::TextureBorderClampUnusedChannelCase(
    Context &context, const char *name, const char *description, uint32_t texFormat,
    tcu::Sampler::DepthStencilMode depthStencilMode)
    : TextureBorderClampTest(context, name, description, texFormat, depthStencilMode,
                             TextureBorderClampTest::STATE_TEXTURE_PARAM, 8, 8, SAMPLE_FILTER, GL_NEAREST)
{
}

static rr::GenericVec4 selectComponents(const rr::GenericVec4 &trueComponents, const rr::GenericVec4 &falseComponents,
                                        const tcu::BVec4 &m)
{
    return rr::GenericVec4(tcu::select(trueComponents.get<uint32_t>(), falseComponents.get<uint32_t>(), m));
}

void TextureBorderClampUnusedChannelCase::init(void)
{
    TextureBorderClampTest::init();

    // \note TextureBorderClampTest::init() creates texture
    const tcu::TextureFormat texFormat =
        tcu::getEffectiveDepthStencilTextureFormat(getTexture()->getRefTexture().getFormat(), m_sampleMode);
    const tcu::TextureFormatInfo texFormatInfo = tcu::getTextureFormatInfo(texFormat);
    const tcu::BVec4 channelMask               = tcu::getTextureFormatChannelMask(texFormat);
    const float maxChannelValue                = (channelMask[0]) ? (texFormatInfo.valueMax[0]) :
                                                 (channelMask[1]) ? (texFormatInfo.valueMax[1]) :
                                                 (channelMask[2]) ? (texFormatInfo.valueMax[2]) :
                                                                    (texFormatInfo.valueMax[3]);

    const rr::GenericVec4 effectiveColors = mapToFormatColorRepresentable(texFormat, tcu::Vec4(0.6f));
    rr::GenericVec4 nonEffectiveColors;

    switch (m_channelClass)
    {
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
        nonEffectiveColors = rr::GenericVec4(tcu::Vec4(maxChannelValue * 0.8f));
        break;

    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
        nonEffectiveColors = rr::GenericVec4(tcu::Vec4(maxChannelValue * 0.8f).cast<int32_t>());
        break;

    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
        nonEffectiveColors = rr::GenericVec4(tcu::Vec4(maxChannelValue * 0.8f).cast<uint32_t>());
        break;
    default:
        DE_ASSERT(false);
    }

    IterationConfig iteration;
    iteration.p0          = tcu::Vec2(-0.25f, -0.75f);
    iteration.p1          = tcu::Vec2(2.25f, 1.25f);
    iteration.borderColor = selectComponents(effectiveColors, nonEffectiveColors, channelMask);
    iteration.lookupScale = texFormatInfo.lookupScale;
    iteration.lookupBias  = texFormatInfo.lookupBias;
    iteration.minFilter   = GL_NEAREST;
    iteration.magFilter   = GL_NEAREST;
    iteration.sWrapMode   = GL_CLAMP_TO_BORDER;
    iteration.tWrapMode   = GL_CLAMP_TO_BORDER;
    iteration.compareMode = GL_NONE;
    iteration.compareRef  = 0.0f;
    iteration.description = "Setting values to unused border color components";

    m_iterations.push_back(iteration);
}

int TextureBorderClampUnusedChannelCase::getNumIterations(void) const
{
    return (int)m_iterations.size();
}

TextureBorderClampTest::IterationConfig TextureBorderClampUnusedChannelCase::getIteration(int ndx) const
{
    return m_iterations[ndx];
}

class TextureBorderClampPerAxisCase3D : public TestCase
{
public:
    TextureBorderClampPerAxisCase3D(Context &context, const char *name, const char *description, uint32_t texFormat,
                                    SizeType size, uint32_t filter, uint32_t sWrap, uint32_t tWrap, uint32_t rWrap);

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

    void renderTo(tcu::Surface &surface, const glu::TextureTestUtil::ReferenceParams &samplerParams);

    void logParams(const glu::TextureTestUtil::ReferenceParams &samplerParams);

    void verifyImage(const tcu::Surface &image, const glu::TextureTestUtil::ReferenceParams &samplerParams);

    glu::TextureTestUtil::ReferenceParams getSamplerParams(void) const;
    uint32_t getCaseSeed(void) const;

    enum
    {
        VIEWPORT_WIDTH  = 128,
        VIEWPORT_HEIGHT = 128,
    };

    const uint32_t m_texFormat;
    const tcu::TextureChannelClass m_channelClass;
    const tcu::IVec3 m_size;
    const uint32_t m_filter;
    const uint32_t m_sWrap;
    const uint32_t m_tWrap;
    const uint32_t m_rWrap;

    de::MovePtr<glu::Texture3D> m_texture;
    de::MovePtr<gls::TextureTestUtil::TextureRenderer> m_renderer;

    rr::GenericVec4 m_borderColor;
    std::vector<float> m_texCoords;
    tcu::Vec4 m_lookupScale;
    tcu::Vec4 m_lookupBias;
};

TextureBorderClampPerAxisCase3D::TextureBorderClampPerAxisCase3D(Context &context, const char *name,
                                                                 const char *description, uint32_t texFormat,
                                                                 SizeType size, uint32_t filter, uint32_t sWrap,
                                                                 uint32_t tWrap, uint32_t rWrap)
    : TestCase(context, name, description)
    , m_texFormat(texFormat)
    , m_channelClass(getFormatChannelClass(texFormat, tcu::Sampler::MODE_LAST))
    , m_size((size == SIZE_POT) ? (tcu::IVec3(8, 16, 4)) : (tcu::IVec3(13, 5, 7)))
    , m_filter(filter)
    , m_sWrap(sWrap)
    , m_tWrap(tWrap)
    , m_rWrap(rWrap)
{
}

void TextureBorderClampPerAxisCase3D::init(void)
{
    auto ctxType            = m_context.getRenderContext().getType();
    const bool isES32orGL45 = glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
                              glu::contextSupports(ctxType, glu::ApiType::core(4, 5));
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(ctxType);

    // repeat filterable test with valid context
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const bool is_texture_float_linear_supported =
        glu::hasExtension(gl, glu::ApiType::es(3, 0), "GL_OES_texture_float_linear");
    const bool coreFilterable =
        isCoreFilterableFormat(m_texFormat, tcu::Sampler::MODE_LAST, is_texture_float_linear_supported);

    if (!coreFilterable && filterRequiresFilterability(m_filter))
        throw tcu::NotSupportedError("Test requires GL_OES_texture_float_linear extension");

    if (!isES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_border_clamp"))
        throw tcu::NotSupportedError("Test requires GL_EXT_texture_border_clamp extension");

    if (glu::isCompressedFormat(m_texFormat) && !isES32orGL45 &&
        tcu::isAstcFormat(glu::mapGLCompressedTexFormat(m_texFormat)) &&
        !m_context.getContextInfo().isExtensionSupported("GL_KHR_texture_compression_astc_ldr"))
    {
        throw tcu::NotSupportedError("Test requires GL_KHR_texture_compression_astc_ldr extension");
    }
    if (m_texFormat == GL_BGRA && !m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_format_BGRA8888"))
        throw tcu::NotSupportedError("Test requires GL_EXT_texture_format_BGRA8888 extension");
    if (m_context.getRenderTarget().getWidth() < VIEWPORT_WIDTH ||
        m_context.getRenderTarget().getHeight() < VIEWPORT_HEIGHT)
    {
        throw tcu::NotSupportedError("Test requires " + de::toString<int>(VIEWPORT_WIDTH) + "x" +
                                     de::toString<int>(VIEWPORT_HEIGHT) + " viewport");
    }

    // resources
    m_texture = genDefaultTexture<glu::Texture3D>(m_context.getRenderContext(), m_context.getContextInfo(), m_texFormat,
                                                  m_size);
    m_renderer = de::MovePtr<gls::TextureTestUtil::TextureRenderer>(new gls::TextureTestUtil::TextureRenderer(
        m_context.getRenderContext(), m_testCtx.getLog(), glslVersion, glu::PRECISION_HIGHP));

    // texture info
    m_testCtx.getLog() << tcu::TestLog::Message << "Created 3D texture with format "
                       << glu::getTextureFormatName(m_texFormat) << ", size (" << m_texture->getRefTexture().getWidth()
                       << ", " << m_texture->getRefTexture().getHeight() << ", "
                       << m_texture->getRefTexture().getDepth() << ")\n"
                       << tcu::TestLog::EndMessage;

    // tex coord
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Setting tex coords bottom-left: (-1, -1, -1.5), top-right (2, 2, 2.5)\n"
                           << tcu::TestLog::EndMessage;

        m_texCoords.resize(4 * 3);

        m_texCoords[0]  = -1.0f;
        m_texCoords[1]  = -1.0f;
        m_texCoords[2]  = -1.5f;
        m_texCoords[3]  = -1.0f;
        m_texCoords[4]  = 2.0f;
        m_texCoords[5]  = 0.5f;
        m_texCoords[6]  = 2.0f;
        m_texCoords[7]  = -1.0f;
        m_texCoords[8]  = 0.5f;
        m_texCoords[9]  = 2.0f;
        m_texCoords[10] = 2.0f;
        m_texCoords[11] = 2.5f;
    }

    // set render params
    {
        const tcu::TextureFormat texFormat         = m_texture->getRefTexture().getFormat();
        const tcu::TextureFormatInfo texFormatInfo = tcu::getTextureFormatInfo(texFormat);

        m_borderColor = mapToFormatColorRepresentable(texFormat, tcu::Vec4(0.2f, 0.6f, 0.9f, 0.4f));

        m_lookupScale = texFormatInfo.lookupScale;
        m_lookupBias  = texFormatInfo.lookupBias;
    }
}

void TextureBorderClampPerAxisCase3D::deinit(void)
{
    m_texture.clear();
    m_renderer.clear();
}

TextureBorderClampPerAxisCase3D::IterateResult TextureBorderClampPerAxisCase3D::iterate(void)
{
    tcu::Surface renderedFrame(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
    const glu::TextureTestUtil::ReferenceParams samplerParams = getSamplerParams();

    logParams(samplerParams);
    renderTo(renderedFrame, samplerParams);
    verifyImage(renderedFrame, samplerParams);

    return STOP;
}

void TextureBorderClampPerAxisCase3D::logParams(const glu::TextureTestUtil::ReferenceParams &samplerParams)
{
    const std::string borderColorString =
        (m_channelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER)   ? (de::toString(m_borderColor.get<int32_t>())) :
        (m_channelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER) ? (de::toString(m_borderColor.get<uint32_t>())) :
                                                                        (de::toString(m_borderColor.get<float>()));

    m_testCtx.getLog() << tcu::TestLog::Message << "Border color is " << borderColorString << "\n"
                       << "Texture lookup bias: " << samplerParams.colorBias << "\n"
                       << "Texture lookup scale: " << samplerParams.colorScale << "\n"
                       << "Filter: " << glu::getTextureFilterName(m_filter) << "\n"
                       << "Wrap mode: s = " << glu::getRepeatModeStr(m_sWrap)
                       << ", t = " << glu::getRepeatModeStr(m_tWrap) << ", r = " << glu::getRepeatModeStr(m_rWrap)
                       << "\n"
                       << tcu::TestLog::EndMessage;
}

void TextureBorderClampPerAxisCase3D::renderTo(tcu::Surface &surface,
                                               const glu::TextureTestUtil::ReferenceParams &samplerParams)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const gls::TextureTestUtil::RandomViewport viewport(m_context.getRenderTarget(), VIEWPORT_WIDTH, VIEWPORT_HEIGHT,
                                                        getCaseSeed());

    // Bind to unit 0.
    gl.activeTexture(GL_TEXTURE0);
    gl.bindTexture(GL_TEXTURE_3D, m_texture->getGLTexture());

    // Setup filtering and wrap modes.
    gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, glu::getGLWrapMode(samplerParams.sampler.wrapS));
    gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, glu::getGLWrapMode(samplerParams.sampler.wrapT));
    gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, glu::getGLWrapMode(samplerParams.sampler.wrapR));
    gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(samplerParams.sampler.minFilter));
    gl.texParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(samplerParams.sampler.magFilter));

    switch (m_channelClass)
    {
    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
        gl.texParameterfv(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, m_borderColor.getAccess<float>());
        break;

    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
        gl.texParameterIiv(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, m_borderColor.getAccess<int32_t>());
        break;

    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
        gl.texParameterIuiv(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, m_borderColor.getAccess<uint32_t>());
        break;

    default:
        DE_ASSERT(false);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "Set texturing state");

    gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
    m_renderer->renderQuad(0, &m_texCoords[0], samplerParams);
    glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, surface.getAccess());
}

void TextureBorderClampPerAxisCase3D::verifyImage(const tcu::Surface &renderedFrame,
                                                  const glu::TextureTestUtil::ReferenceParams &samplerParams)
{
    const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();
    const int colorErrorBits           = 2;
    const tcu::IVec4 colorBits =
        tcu::max(glu::TextureTestUtil::getBitsVec(pixelFormat) - tcu::IVec4(colorErrorBits), tcu::IVec4(0));
    tcu::Surface reference(renderedFrame.getWidth(), renderedFrame.getHeight());
    tcu::Surface errorMask(renderedFrame.getWidth(), renderedFrame.getHeight());
    tcu::LodPrecision lodPrecision;
    tcu::LookupPrecision lookupPrecision;
    int numFailedPixels;

    lodPrecision.derivateBits = 18;
    lodPrecision.lodBits      = 5;

    lookupPrecision.colorThreshold = tcu::computeFixedPointThreshold(colorBits) / samplerParams.colorScale;
    lookupPrecision.coordBits      = tcu::IVec3(20, 20, 0);
    lookupPrecision.uvwBits        = tcu::IVec3(5, 5, 0);
    lookupPrecision.colorMask      = glu::TextureTestUtil::getCompareMask(pixelFormat);

    glu::TextureTestUtil::sampleTexture(tcu::SurfaceAccess(reference, pixelFormat), m_texture->getRefTexture(),
                                        &m_texCoords[0], samplerParams);

    numFailedPixels = glu::TextureTestUtil::computeTextureLookupDiff(
        renderedFrame.getAccess(), reference.getAccess(), errorMask.getAccess(), m_texture->getRefTexture(),
        &m_texCoords[0], samplerParams, lookupPrecision, lodPrecision, m_testCtx.getWatchDog());

    if (numFailedPixels > 0)
        m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Result verification failed, got " << numFailedPixels
                           << " invalid pixels!" << tcu::TestLog::EndMessage;
    m_testCtx.getLog() << tcu::TestLog::ImageSet("VerifyResult", "Verification result")
                       << tcu::TestLog::Image("Rendered", "Rendered image", renderedFrame);
    if (numFailedPixels > 0)
    {
        m_testCtx.getLog() << tcu::TestLog::Image("Reference", "Ideal reference image", reference)
                           << tcu::TestLog::Image("ErrorMask", "Error mask", errorMask);
    }
    m_testCtx.getLog() << tcu::TestLog::EndImageSet;

    if (numFailedPixels == 0)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
}

glu::TextureTestUtil::ReferenceParams TextureBorderClampPerAxisCase3D::getSamplerParams(void) const
{
    const tcu::TextureFormat texFormat = m_texture->getRefTexture().getFormat();
    glu::TextureTestUtil::ReferenceParams refParams(glu::TextureTestUtil::TEXTURETYPE_3D);

    refParams.sampler             = glu::mapGLSampler(m_sWrap, m_tWrap, m_rWrap, m_filter, m_filter);
    refParams.sampler.borderColor = m_borderColor;
    refParams.lodMode             = glu::TextureTestUtil::LODMODE_EXACT;
    refParams.samplerType         = glu::TextureTestUtil::getSamplerType(texFormat);
    refParams.colorScale          = m_lookupScale;
    refParams.colorBias           = m_lookupBias;

    return refParams;
}

uint32_t TextureBorderClampPerAxisCase3D::getCaseSeed(void) const
{
    tcu::SeedBuilder builder;
    builder << std::string(getName()) << m_texFormat << m_filter << m_sWrap << m_tWrap << m_rWrap
            << m_texture->getRefTexture().getWidth() << m_texture->getRefTexture().getHeight()
            << m_texture->getRefTexture().getDepth();
    return builder.get();
}

static bool isFormatSupported(uint32_t format, bool isGL45)
{
    if (isGL45 && (format == GL_LUMINANCE || format == GL_ALPHA || format == GL_LUMINANCE_ALPHA))
        return false;

    return true;
}

} // namespace

TextureBorderClampTests::TextureBorderClampTests(Context &context, bool isGL45)
    : TestCaseGroup(context, "border_clamp", "EXT_texture_border_clamp tests")
    , m_isGL45(isGL45)
{
}

TextureBorderClampTests::~TextureBorderClampTests(void)
{
}

void TextureBorderClampTests::init(void)
{
    static const struct
    {
        const char *name;
        uint32_t filter;
        TextureBorderClampTest::SamplingFunction sampling;
    } s_filters[] = {
        {"nearest", GL_NEAREST, TextureBorderClampTest::SAMPLE_FILTER},
        {"linear", GL_LINEAR, TextureBorderClampTest::SAMPLE_FILTER},
        {"gather", GL_NEAREST, TextureBorderClampTest::SAMPLE_GATHER},
    };

    // .formats
    {
        static const struct
        {
            const char *name;
            uint32_t format;
            tcu::Sampler::DepthStencilMode mode;
        } formats[] = {
            {"luminance", GL_LUMINANCE, tcu::Sampler::MODE_LAST},
            {"alpha", GL_ALPHA, tcu::Sampler::MODE_LAST},
            {"luminance_alpha", GL_LUMINANCE_ALPHA, tcu::Sampler::MODE_LAST},
            {"bgra", GL_BGRA, tcu::Sampler::MODE_LAST},
            {"r8", GL_R8, tcu::Sampler::MODE_LAST},
            {"r8_snorm", GL_R8_SNORM, tcu::Sampler::MODE_LAST},
            {"rg8", GL_RG8, tcu::Sampler::MODE_LAST},
            {"rg8_snorm", GL_RG8_SNORM, tcu::Sampler::MODE_LAST},
            {"rgb8", GL_RGB8, tcu::Sampler::MODE_LAST},
            {"rgb8_snorm", GL_RGB8_SNORM, tcu::Sampler::MODE_LAST},
            {"rgb565", GL_RGB565, tcu::Sampler::MODE_LAST},
            {"rgba4", GL_RGBA4, tcu::Sampler::MODE_LAST},
            {"rgb5_a1", GL_RGB5_A1, tcu::Sampler::MODE_LAST},
            {"rgba8", GL_RGBA8, tcu::Sampler::MODE_LAST},
            {"rgba8_snorm", GL_RGBA8_SNORM, tcu::Sampler::MODE_LAST},
            {"rgb10_a2", GL_RGB10_A2, tcu::Sampler::MODE_LAST},
            {"rgb10_a2ui", GL_RGB10_A2UI, tcu::Sampler::MODE_LAST},
            {"srgb8", GL_SRGB8, tcu::Sampler::MODE_LAST},
            {"srgb8_alpha8", GL_SRGB8_ALPHA8, tcu::Sampler::MODE_LAST},
            {"r16f", GL_R16F, tcu::Sampler::MODE_LAST},
            {"rg16f", GL_RG16F, tcu::Sampler::MODE_LAST},
            {"rgb16f", GL_RGB16F, tcu::Sampler::MODE_LAST},
            {"rgba16f", GL_RGBA16F, tcu::Sampler::MODE_LAST},
            {"r32f", GL_R32F, tcu::Sampler::MODE_LAST},
            {"rg32f", GL_RG32F, tcu::Sampler::MODE_LAST},
            {"rgb32f", GL_RGB32F, tcu::Sampler::MODE_LAST},
            {"rgba32f", GL_RGBA32F, tcu::Sampler::MODE_LAST},
            {"r11f_g11f_b10f", GL_R11F_G11F_B10F, tcu::Sampler::MODE_LAST},
            {"rgb9_e5", GL_RGB9_E5, tcu::Sampler::MODE_LAST},
            {"r8i", GL_R8I, tcu::Sampler::MODE_LAST},
            {"r8ui", GL_R8UI, tcu::Sampler::MODE_LAST},
            {"r16i", GL_R16I, tcu::Sampler::MODE_LAST},
            {"r16ui", GL_R16UI, tcu::Sampler::MODE_LAST},
            {"r32i", GL_R32I, tcu::Sampler::MODE_LAST},
            {"r32ui", GL_R32UI, tcu::Sampler::MODE_LAST},
            {"rg8i", GL_RG8I, tcu::Sampler::MODE_LAST},
            {"rg8ui", GL_RG8UI, tcu::Sampler::MODE_LAST},
            {"rg16i", GL_RG16I, tcu::Sampler::MODE_LAST},
            {"rg16ui", GL_RG16UI, tcu::Sampler::MODE_LAST},
            {"rg32i", GL_RG32I, tcu::Sampler::MODE_LAST},
            {"rg32ui", GL_RG32UI, tcu::Sampler::MODE_LAST},
            {"rgb8i", GL_RGB8I, tcu::Sampler::MODE_LAST},
            {"rgb8ui", GL_RGB8UI, tcu::Sampler::MODE_LAST},
            {"rgb16i", GL_RGB16I, tcu::Sampler::MODE_LAST},
            {"rgb16ui", GL_RGB16UI, tcu::Sampler::MODE_LAST},
            {"rgb32i", GL_RGB32I, tcu::Sampler::MODE_LAST},
            {"rgb32ui", GL_RGB32UI, tcu::Sampler::MODE_LAST},
            {"rgba8i", GL_RGBA8I, tcu::Sampler::MODE_LAST},
            {"rgba8ui", GL_RGBA8UI, tcu::Sampler::MODE_LAST},
            {"rgba16i", GL_RGBA16I, tcu::Sampler::MODE_LAST},
            {"rgba16ui", GL_RGBA16UI, tcu::Sampler::MODE_LAST},
            {"rgba32i", GL_RGBA32I, tcu::Sampler::MODE_LAST},
            {"rgba32ui", GL_RGBA32UI, tcu::Sampler::MODE_LAST},
            {"depth_component16", GL_DEPTH_COMPONENT16, tcu::Sampler::MODE_DEPTH},
            {"depth_component24", GL_DEPTH_COMPONENT24, tcu::Sampler::MODE_DEPTH},
            {"depth_component32f", GL_DEPTH_COMPONENT32F, tcu::Sampler::MODE_DEPTH},
            {"stencil_index8", GL_STENCIL_INDEX8, tcu::Sampler::MODE_STENCIL},
            {"depth24_stencil8_sample_depth", GL_DEPTH24_STENCIL8, tcu::Sampler::MODE_DEPTH},
            {"depth32f_stencil8_sample_depth", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_DEPTH},
            {"depth24_stencil8_sample_stencil", GL_DEPTH24_STENCIL8, tcu::Sampler::MODE_STENCIL},
            {"depth32f_stencil8_sample_stencil", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_STENCIL},
            {"compressed_r11_eac", GL_COMPRESSED_R11_EAC, tcu::Sampler::MODE_LAST},
            {"compressed_signed_r11_eac", GL_COMPRESSED_SIGNED_R11_EAC, tcu::Sampler::MODE_LAST},
            {"compressed_rg11_eac", GL_COMPRESSED_RG11_EAC, tcu::Sampler::MODE_LAST},
            {"compressed_signed_rg11_eac", GL_COMPRESSED_SIGNED_RG11_EAC, tcu::Sampler::MODE_LAST},
            {"compressed_rgb8_etc2", GL_COMPRESSED_RGB8_ETC2, tcu::Sampler::MODE_LAST},
            {"compressed_srgb8_etc2", GL_COMPRESSED_SRGB8_ETC2, tcu::Sampler::MODE_LAST},
            {"compressed_rgb8_punchthrough_alpha1_etc2", GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
             tcu::Sampler::MODE_LAST},
            {"compressed_srgb8_punchthrough_alpha1_etc2", GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
             tcu::Sampler::MODE_LAST},
            {"compressed_rgba8_etc2_eac", GL_COMPRESSED_RGBA8_ETC2_EAC, tcu::Sampler::MODE_LAST},
            {"compressed_srgb8_alpha8_etc2_eac", GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, tcu::Sampler::MODE_LAST},
        };

        tcu::TestCaseGroup *const formatsGroup = new tcu::TestCaseGroup(m_testCtx, "formats", "Format tests");
        addChild(formatsGroup);

        // .format
        for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); ++formatNdx)
        {
            const uint32_t format                           = formats[formatNdx].format;
            const tcu::Sampler::DepthStencilMode sampleMode = formats[formatNdx].mode;
            const bool isCompressed                         = glu::isCompressedFormat(format);
            const bool coreFilterable                       = isCoreFilterableFormat(format, sampleMode);
            tcu::TestCaseGroup *const formatGroup =
                new tcu::TestCaseGroup(m_testCtx, formats[formatNdx].name, "Format test");

            formatsGroup->addChild(formatGroup);

            // .nearest
            // .linear
            for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(s_filters); ++filterNdx)
            {
                // [not-compressed]
                // .size_pot
                // .size_npot
                // [compressed]
                // .size_tile_multiple (also pot)
                // .size_not_tile_multiple (also npot)
                for (int sizeNdx = 0; sizeNdx < 2; ++sizeNdx)
                {
                    const bool isNpotCase          = (sizeNdx == 1);
                    const char *const sizePotName  = (!isCompressed) ? ("size_pot") : ("size_tile_multiple");
                    const char *const sizeNpotName = (!isCompressed) ? ("size_npot") : ("size_not_tile_multiple");
                    const char *const sizeName     = (isNpotCase) ? (sizeNpotName) : (sizePotName);
                    const SizeType sizeType        = (isNpotCase) ? (SIZE_NPOT) : (SIZE_POT);
                    const std::string caseName     = std::string() + s_filters[filterNdx].name + "_" + sizeName;
                    const uint32_t filter          = s_filters[filterNdx].filter;

                    if ((coreFilterable || !filterRequiresFilterability(filter)) && isFormatSupported(format, m_isGL45))
                        formatGroup->addChild(
                            new TextureBorderClampFormatCase(m_context, caseName.c_str(), "", format, sampleMode,
                                                             TextureBorderClampFormatCase::STATE_TEXTURE_PARAM,
                                                             sizeType, filter, s_filters[filterNdx].sampling));
                }
            }
        }
    }

    // .range_clamp
    {
        static const struct
        {
            const char *name;
            uint32_t format;
            tcu::Sampler::DepthStencilMode mode;
        } formats[] = {
            {"unorm_color", GL_R8, tcu::Sampler::MODE_LAST},
            {"snorm_color", GL_R8_SNORM, tcu::Sampler::MODE_LAST},
            {"float_color", GL_RG32F, tcu::Sampler::MODE_LAST},
            {"int_color", GL_R8I, tcu::Sampler::MODE_LAST},
            {"uint_color", GL_R16UI, tcu::Sampler::MODE_LAST},
            {"srgb_color", GL_SRGB8_ALPHA8, tcu::Sampler::MODE_LAST},
            {"unorm_depth", GL_DEPTH_COMPONENT24, tcu::Sampler::MODE_DEPTH},
            {"float_depth", GL_DEPTH_COMPONENT32F, tcu::Sampler::MODE_DEPTH},
            {"uint_stencil", GL_STENCIL_INDEX8, tcu::Sampler::MODE_STENCIL},
            {"float_depth_uint_stencil_sample_depth", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_DEPTH},
            {"float_depth_uint_stencil_sample_stencil", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_STENCIL},
            {"unorm_depth_uint_stencil_sample_depth", GL_DEPTH24_STENCIL8, tcu::Sampler::MODE_DEPTH},
            {"unorm_depth_uint_stencil_sample_stencil", GL_DEPTH24_STENCIL8, tcu::Sampler::MODE_STENCIL},
            {"compressed_color", GL_COMPRESSED_RG11_EAC, tcu::Sampler::MODE_LAST},
        };

        tcu::TestCaseGroup *const rangeClampGroup =
            new tcu::TestCaseGroup(m_testCtx, "range_clamp", "Range clamp tests");
        addChild(rangeClampGroup);

        for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); ++formatNdx)
            for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(s_filters); ++filterNdx)
            {
                const uint32_t format                           = formats[formatNdx].format;
                const tcu::Sampler::DepthStencilMode sampleMode = formats[formatNdx].mode;
                const std::string caseName = std::string() + s_filters[filterNdx].name + "_" + formats[formatNdx].name;
                const uint32_t filter      = s_filters[filterNdx].filter;
                const bool coreFilterable  = isCoreFilterableFormat(format, sampleMode);

                if (s_filters[filterNdx].sampling == TextureBorderClampTest::SAMPLE_GATHER)
                    continue;

                if (coreFilterable || !filterRequiresFilterability(filter))
                    rangeClampGroup->addChild(new TextureBorderClampRangeClampCase(m_context, caseName.c_str(), "",
                                                                                   format, sampleMode, filter));
            }
    }

    // .sampler
    {
        static const struct
        {
            const char *name;
            uint32_t format;
            tcu::Sampler::DepthStencilMode mode;
        } formats[] = {
            {"unorm_color", GL_R8, tcu::Sampler::MODE_LAST},
            {"snorm_color", GL_R8_SNORM, tcu::Sampler::MODE_LAST},
            {"float_color", GL_RG32F, tcu::Sampler::MODE_LAST},
            {"int_color", GL_R8I, tcu::Sampler::MODE_LAST},
            {"uint_color", GL_R16UI, tcu::Sampler::MODE_LAST},
            {"unorm_depth", GL_DEPTH_COMPONENT24, tcu::Sampler::MODE_DEPTH},
            {"float_depth", GL_DEPTH_COMPONENT32F, tcu::Sampler::MODE_DEPTH},
            {"uint_stencil", GL_STENCIL_INDEX8, tcu::Sampler::MODE_STENCIL},
            {"compressed_color", GL_COMPRESSED_RG11_EAC, tcu::Sampler::MODE_LAST},
        };

        tcu::TestCaseGroup *const samplerGroup = new tcu::TestCaseGroup(m_testCtx, "sampler", "Sampler param tests");
        addChild(samplerGroup);

        for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); ++formatNdx)
        {
            const uint32_t format                           = formats[formatNdx].format;
            const tcu::Sampler::DepthStencilMode sampleMode = formats[formatNdx].mode;
            const char *caseName                            = formats[formatNdx].name;

            samplerGroup->addChild(new TextureBorderClampFormatCase(
                m_context, caseName, "", format, sampleMode, TextureBorderClampFormatCase::STATE_SAMPLER_PARAM,
                SIZE_POT, GL_NEAREST, TextureBorderClampFormatCase::SAMPLE_FILTER));
        }
    }

    // .per_axis_wrap_mode
    {
        static const struct
        {
            const char *name;
            bool is3D;
        } targets[] = {
            {"texture_2d", false},
            {"texture_3d", true},
        };
        static const struct
        {
            const char *name;
            uint32_t format;
            tcu::Sampler::DepthStencilMode mode;
            bool supports3D;
        } formats[] = {
            {"unorm_color", GL_RG8, tcu::Sampler::MODE_LAST, true},
            {"snorm_color", GL_RG8_SNORM, tcu::Sampler::MODE_LAST, true},
            {"float_color", GL_R32F, tcu::Sampler::MODE_LAST, true},
            {"int_color", GL_RG16I, tcu::Sampler::MODE_LAST, true},
            {"uint_color", GL_R8UI, tcu::Sampler::MODE_LAST, true},
            {"unorm_depth", GL_DEPTH_COMPONENT16, tcu::Sampler::MODE_DEPTH, false},
            {"float_depth", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_DEPTH, false},
            {"uint_stencil", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_STENCIL, false},
            {"compressed_color", GL_COMPRESSED_RGB8_ETC2, tcu::Sampler::MODE_LAST, false},
        };
        static const struct
        {
            const char *name;
            uint32_t sWrap;
            uint32_t tWrap;
            uint32_t rWrap;
            bool is3D;
        } wrapConfigs[] = {
            // 2d configs
            {"s_clamp_to_edge_t_clamp_to_border", GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER, GL_NONE, false},
            {"s_repeat_t_clamp_to_border", GL_REPEAT, GL_CLAMP_TO_BORDER, GL_NONE, false},
            {"s_mirrored_repeat_t_clamp_to_border", GL_MIRRORED_REPEAT, GL_CLAMP_TO_BORDER, GL_NONE, false},

            // 3d configs
            {"s_clamp_to_border_t_clamp_to_border_r_clamp_to_border", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER,
             GL_CLAMP_TO_BORDER, true},
            {"s_clamp_to_border_t_clamp_to_border_r_repeat", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER, GL_REPEAT, true},
            {"s_mirrored_repeat_t_clamp_to_border_r_repeat", GL_MIRRORED_REPEAT, GL_CLAMP_TO_BORDER, GL_REPEAT, true},
            {"s_repeat_t_mirrored_repeat_r_clamp_to_border", GL_REPEAT, GL_MIRRORED_REPEAT, GL_CLAMP_TO_BORDER, true},
        };

        tcu::TestCaseGroup *const perAxisGroup =
            new tcu::TestCaseGroup(m_testCtx, "per_axis_wrap_mode", "Per-axis wrapping modes");
        addChild(perAxisGroup);

        // .texture_nd
        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        {
            tcu::TestCaseGroup *const targetGroup =
                new tcu::TestCaseGroup(m_testCtx, targets[targetNdx].name, "Texture target test");
            perAxisGroup->addChild(targetGroup);

            // .format
            for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); ++formatNdx)
            {
                if (targets[targetNdx].is3D && !formats[formatNdx].supports3D)
                    continue;
                else
                {
                    const uint32_t format                           = formats[formatNdx].format;
                    const tcu::Sampler::DepthStencilMode sampleMode = formats[formatNdx].mode;
                    const bool coreFilterable                       = isCoreFilterableFormat(format, sampleMode);
                    tcu::TestCaseGroup *const formatGroup =
                        new tcu::TestCaseGroup(m_testCtx, formats[formatNdx].name, "Format test");
                    targetGroup->addChild(formatGroup);

                    // .linear
                    // .nearest
                    // .gather
                    for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(s_filters); ++filterNdx)
                    {
                        const uint32_t filter = s_filters[filterNdx].filter;

                        if (!coreFilterable && filterRequiresFilterability(filter))
                        {
                            // skip linear on pure integers
                            continue;
                        }
                        else if (s_filters[filterNdx].sampling == TextureBorderClampTest::SAMPLE_GATHER &&
                                 targets[targetNdx].is3D)
                        {
                            // skip gather on 3d
                            continue;
                        }
                        else
                        {
                            tcu::TestCaseGroup *const filteringGroup = new tcu::TestCaseGroup(
                                m_testCtx, s_filters[filterNdx].name, "Tests with specific filter");
                            formatGroup->addChild(filteringGroup);

                            // .s_XXX_t_XXX(_r_XXX)
                            for (int wrapNdx = 0; wrapNdx < DE_LENGTH_OF_ARRAY(wrapConfigs); ++wrapNdx)
                            {
                                if (wrapConfigs[wrapNdx].is3D != targets[targetNdx].is3D)
                                    continue;
                                else
                                {
                                    for (int sizeNdx = 0; sizeNdx < 2; ++sizeNdx)
                                    {
                                        const char *const wrapName          = wrapConfigs[wrapNdx].name;
                                        const bool isNpotCase               = (sizeNdx == 1);
                                        const char *const sizeNameExtension = (isNpotCase) ? ("_npot") : ("_pot");
                                        const SizeType size                 = (isNpotCase) ? (SIZE_NPOT) : (SIZE_POT);

                                        if (!targets[targetNdx].is3D)
                                            filteringGroup->addChild(new TextureBorderClampPerAxisCase2D(
                                                m_context, (std::string() + wrapName + sizeNameExtension).c_str(), "",
                                                format, sampleMode, size, filter, wrapConfigs[wrapNdx].sWrap,
                                                wrapConfigs[wrapNdx].tWrap, s_filters[filterNdx].sampling));
                                        else
                                        {
                                            DE_ASSERT(sampleMode == tcu::Sampler::MODE_LAST);
                                            filteringGroup->addChild(new TextureBorderClampPerAxisCase3D(
                                                m_context, (std::string() + wrapName + sizeNameExtension).c_str(), "",
                                                format, size, filter, wrapConfigs[wrapNdx].sWrap,
                                                wrapConfigs[wrapNdx].tWrap, wrapConfigs[wrapNdx].rWrap));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // .depth_compare_mode
    {
        static const struct
        {
            const char *name;
            uint32_t format;
        } formats[] = {
            {"depth_component16", GL_DEPTH_COMPONENT16},
            {"depth_component24", GL_DEPTH_COMPONENT24},
            {"depth24_stencil8", GL_DEPTH24_STENCIL8},
            {"depth32f_stencil8", GL_DEPTH32F_STENCIL8},
        };

        tcu::TestCaseGroup *const compareGroup =
            new tcu::TestCaseGroup(m_testCtx, "depth_compare_mode", "Tests depth compare mode");
        addChild(compareGroup);

        for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); ++formatNdx)
        {
            const uint32_t format = formats[formatNdx].format;
            tcu::TestCaseGroup *const formatGroup =
                new tcu::TestCaseGroup(m_testCtx, formats[formatNdx].name, "Format test");

            compareGroup->addChild(formatGroup);

            // (format).(linear|nearest|gather)_(pot|npot)
            for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(s_filters); ++filterNdx)
                for (int sizeNdx = 0; sizeNdx < 2; ++sizeNdx)
                {
                    const bool isNpotCase      = (sizeNdx == 1);
                    const char *const sizeName = (isNpotCase) ? ("size_npot") : ("size_pot");
                    const SizeType sizeType    = (isNpotCase) ? (SIZE_NPOT) : (SIZE_POT);
                    const std::string caseName = std::string() + s_filters[filterNdx].name + "_" + sizeName;
                    const uint32_t filter      = s_filters[filterNdx].filter;

                    formatGroup->addChild(new TextureBorderClampDepthCompareCase(
                        m_context, caseName.c_str(), "", format, sizeType, filter, s_filters[filterNdx].sampling));
                }
        }
    }

    // unused channels (A in rgb, G in stencil etc.)
    {
        static const struct
        {
            const char *name;
            uint32_t format;
            tcu::Sampler::DepthStencilMode mode;
        } formats[] = {
            {"r8", GL_R8, tcu::Sampler::MODE_LAST},
            {"rg8_snorm", GL_RG8_SNORM, tcu::Sampler::MODE_LAST},
            {"rgb8", GL_RGB8, tcu::Sampler::MODE_LAST},
            {"rg32f", GL_RG32F, tcu::Sampler::MODE_LAST},
            {"r16i", GL_RG16I, tcu::Sampler::MODE_LAST},
            {"luminance", GL_LUMINANCE, tcu::Sampler::MODE_LAST},
            {"alpha", GL_ALPHA, tcu::Sampler::MODE_LAST},
            {"luminance_alpha", GL_LUMINANCE_ALPHA, tcu::Sampler::MODE_LAST},
            {"depth_component16", GL_DEPTH_COMPONENT16, tcu::Sampler::MODE_DEPTH},
            {"depth_component32f", GL_DEPTH_COMPONENT32F, tcu::Sampler::MODE_DEPTH},
            {"stencil_index8", GL_STENCIL_INDEX8, tcu::Sampler::MODE_STENCIL},
            {"depth32f_stencil8_sample_depth", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_DEPTH},
            {"depth32f_stencil8_sample_stencil", GL_DEPTH32F_STENCIL8, tcu::Sampler::MODE_STENCIL},
            {"depth24_stencil8_sample_depth", GL_DEPTH24_STENCIL8, tcu::Sampler::MODE_DEPTH},
            {"depth24_stencil8_sample_stencil", GL_DEPTH24_STENCIL8, tcu::Sampler::MODE_STENCIL},
            {"compressed_r11_eac", GL_COMPRESSED_R11_EAC, tcu::Sampler::MODE_LAST},
        };

        tcu::TestCaseGroup *const unusedGroup = new tcu::TestCaseGroup(
            m_testCtx, "unused_channels", "Tests channels that are not present in the internal format");
        addChild(unusedGroup);

        for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(formats); ++formatNdx)
        {
            if (isFormatSupported(formats[formatNdx].format, m_isGL45))
            {
                unusedGroup->addChild(new TextureBorderClampUnusedChannelCase(
                    m_context, formats[formatNdx].name, "", formats[formatNdx].format, formats[formatNdx].mode));
            }
        }
    }
}

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