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

#include "es31fTextureFilteringTests.hpp"

#include "glsTextureTestUtil.hpp"

#include "gluPixelTransfer.hpp"
#include "gluTexture.hpp"
#include "gluTextureUtil.hpp"

#include "tcuCommandLine.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuTexLookupVerifier.hpp"
#include "tcuVectorUtil.hpp"

#include "deStringUtil.hpp"
#include "deString.h"

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

namespace deqp
{
namespace gles31
{
namespace Functional
{

using std::string;
using std::vector;
using tcu::TestLog;
using namespace gls::TextureTestUtil;
using namespace glu::TextureTestUtil;

static const char *getFaceDesc(const tcu::CubeFace face)
{
    switch (face)
    {
    case tcu::CUBEFACE_NEGATIVE_X:
        return "-X";
    case tcu::CUBEFACE_POSITIVE_X:
        return "+X";
    case tcu::CUBEFACE_NEGATIVE_Y:
        return "-Y";
    case tcu::CUBEFACE_POSITIVE_Y:
        return "+Y";
    case tcu::CUBEFACE_NEGATIVE_Z:
        return "-Z";
    case tcu::CUBEFACE_POSITIVE_Z:
        return "+Z";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static void logCubeArrayTexCoords(TestLog &log, vector<float> &texCoord)
{
    const size_t numVerts = texCoord.size() / 4;

    DE_ASSERT(texCoord.size() % 4 == 0);

    for (size_t vertNdx = 0; vertNdx < numVerts; vertNdx++)
    {
        const size_t coordNdx = vertNdx * 4;

        const float u = texCoord[coordNdx + 0];
        const float v = texCoord[coordNdx + 1];
        const float w = texCoord[coordNdx + 2];
        const float q = texCoord[coordNdx + 3];

        log << TestLog::Message << vertNdx << ": (" << u << ", " << v << ", " << w << ", " << q << ")"
            << TestLog::EndMessage;
    }
}

// Cube map array filtering

class TextureCubeArrayFilteringCase : public TestCase
{
public:
    TextureCubeArrayFilteringCase(Context &context, const char *name, const char *desc, uint32_t minFilter,
                                  uint32_t magFilter, uint32_t wrapS, uint32_t wrapT, uint32_t internalFormat, int size,
                                  int depth, bool onlySampleFaceInterior = false);

    ~TextureCubeArrayFilteringCase(void);

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

private:
    TextureCubeArrayFilteringCase(const TextureCubeArrayFilteringCase &);
    TextureCubeArrayFilteringCase &operator=(const TextureCubeArrayFilteringCase &);

    const uint32_t m_minFilter;
    const uint32_t m_magFilter;
    const uint32_t m_wrapS;
    const uint32_t m_wrapT;

    const uint32_t m_internalFormat;
    const int m_size;
    const int m_depth;

    const bool m_onlySampleFaceInterior; //!< If true, we avoid sampling anywhere near a face's edges.

    struct FilterCase
    {
        const glu::TextureCubeArray *texture;
        tcu::Vec2 bottomLeft;
        tcu::Vec2 topRight;
        tcu::Vec2 layerRange;

        FilterCase(void) : texture(DE_NULL)
        {
        }

        FilterCase(const glu::TextureCubeArray *tex_, const tcu::Vec2 &bottomLeft_, const tcu::Vec2 &topRight_,
                   const tcu::Vec2 &layerRange_)
            : texture(tex_)
            , bottomLeft(bottomLeft_)
            , topRight(topRight_)
            , layerRange(layerRange_)
        {
        }
    };

    glu::TextureCubeArray *m_gradientTex;
    glu::TextureCubeArray *m_gridTex;

    TextureRenderer m_renderer;

    std::vector<FilterCase> m_cases;
    int m_caseNdx;
};

TextureCubeArrayFilteringCase::TextureCubeArrayFilteringCase(Context &context, const char *name, const char *desc,
                                                             uint32_t minFilter, uint32_t magFilter, uint32_t wrapS,
                                                             uint32_t wrapT, uint32_t internalFormat, int size,
                                                             int depth, bool onlySampleFaceInterior)
    : TestCase(context, name, desc)
    , m_minFilter(minFilter)
    , m_magFilter(magFilter)
    , m_wrapS(wrapS)
    , m_wrapT(wrapT)
    , m_internalFormat(internalFormat)
    , m_size(size)
    , m_depth(depth)
    , m_onlySampleFaceInterior(onlySampleFaceInterior)
    , m_gradientTex(DE_NULL)
    , m_gridTex(DE_NULL)
    , m_renderer(context.getRenderContext(), context.getTestContext().getLog(), glu::GLSL_VERSION_310_ES,
                 glu::PRECISION_HIGHP)
    , m_caseNdx(0)
{
}

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

void TextureCubeArrayFilteringCase::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));

    if (!isES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_cube_map_array"))
        throw tcu::NotSupportedError("GL_EXT_texture_cube_map_array not supported");

    if (m_internalFormat == GL_SR8_EXT && !(m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_sRGB_R8")))
        TCU_THROW(NotSupportedError, "GL_EXT_texture_sRGB_R8 not supported");

    if (m_internalFormat == GL_SRG8_EXT &&
        !(m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_sRGB_RG8")))
        TCU_THROW(NotSupportedError, "GL_EXT_texture_sRGB_RG8 not supported");

    try
    {
        const glw::Functions &gl             = m_context.getRenderContext().getFunctions();
        const tcu::TextureFormat texFmt      = glu::mapGLInternalFormat(m_internalFormat);
        const tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(texFmt);
        const tcu::Vec4 cScale               = fmtInfo.valueMax - fmtInfo.valueMin;
        const tcu::Vec4 cBias                = fmtInfo.valueMin;
        const int numLevels                  = deLog2Floor32(m_size) + 1;
        const int numLayers                  = m_depth / 6;

        if (!isContextTypeES(ctxType))
            gl.enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);

        // Create textures.
        m_gradientTex = new glu::TextureCubeArray(m_context.getRenderContext(), m_internalFormat, m_size, m_depth);
        m_gridTex     = new glu::TextureCubeArray(m_context.getRenderContext(), m_internalFormat, m_size, m_depth);

        const tcu::IVec4 levelSwz[] = {
            tcu::IVec4(0, 1, 2, 3),
            tcu::IVec4(2, 1, 3, 0),
            tcu::IVec4(3, 0, 1, 2),
            tcu::IVec4(1, 3, 2, 0),
        };

        // Fill first gradient texture (gradient direction varies between layers).
        for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
        {
            m_gradientTex->getRefTexture().allocLevel(levelNdx);

            const tcu::PixelBufferAccess levelBuf = m_gradientTex->getRefTexture().getLevel(levelNdx);

            for (int layerFaceNdx = 0; layerFaceNdx < m_depth; layerFaceNdx++)
            {
                const tcu::IVec4 swz = levelSwz[layerFaceNdx % DE_LENGTH_OF_ARRAY(levelSwz)];
                const tcu::Vec4 gMin =
                    tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f).swizzle(swz[0], swz[1], swz[2], swz[3]) * cScale + cBias;
                const tcu::Vec4 gMax =
                    tcu::Vec4(1.0f, 1.0f, 1.0f, 0.0f).swizzle(swz[0], swz[1], swz[2], swz[3]) * cScale + cBias;

                tcu::fillWithComponentGradients(
                    tcu::getSubregion(levelBuf, 0, 0, layerFaceNdx, levelBuf.getWidth(), levelBuf.getHeight(), 1), gMin,
                    gMax);
            }
        }

        // Fill second with grid texture (each layer has unique colors).
        for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
        {
            m_gridTex->getRefTexture().allocLevel(levelNdx);

            const tcu::PixelBufferAccess levelBuf = m_gridTex->getRefTexture().getLevel(levelNdx);

            for (int layerFaceNdx = 0; layerFaceNdx < m_depth; layerFaceNdx++)
            {
                const uint32_t step   = 0x00ffffff / (numLevels * m_depth - 1);
                const uint32_t rgb    = step * (levelNdx + layerFaceNdx * numLevels);
                const uint32_t colorA = 0xff000000 | rgb;
                const uint32_t colorB = 0xff000000 | ~rgb;

                tcu::fillWithGrid(
                    tcu::getSubregion(levelBuf, 0, 0, layerFaceNdx, levelBuf.getWidth(), levelBuf.getHeight(), 1), 4,
                    tcu::RGBA(colorA).toVec() * cScale + cBias, tcu::RGBA(colorB).toVec() * cScale + cBias);
            }
        }

        // Upload.
        m_gradientTex->upload();
        m_gridTex->upload();

        // Test cases
        {
            const glu::TextureCubeArray *const tex0 = m_gradientTex;
            const glu::TextureCubeArray *const tex1 = m_gridTex;

            if (m_onlySampleFaceInterior)
            {
                m_cases.push_back(FilterCase(tex0, tcu::Vec2(-0.8f, -0.8f), tcu::Vec2(0.8f, 0.8f),
                                             tcu::Vec2(-0.5f, float(numLayers) + 0.5f))); // minification
                m_cases.push_back(FilterCase(tex0, tcu::Vec2(0.5f, 0.65f), tcu::Vec2(0.8f, 0.8f),
                                             tcu::Vec2(-0.5f, float(numLayers) + 0.5f))); // magnification
                m_cases.push_back(FilterCase(tex1, tcu::Vec2(-0.8f, -0.8f), tcu::Vec2(0.8f, 0.8f),
                                             tcu::Vec2(float(numLayers) + 0.5f, -0.5f))); // minification
                m_cases.push_back(FilterCase(tex1, tcu::Vec2(0.2f, 0.2f), tcu::Vec2(0.6f, 0.5f),
                                             tcu::Vec2(float(numLayers) + 0.5f, -0.5f))); // magnification
            }
            else
            {
                const bool isSingleSample = (m_context.getRenderTarget().getNumSamples() == 0);

                // minification - w/ tweak to avoid hitting triangle edges with a face switchpoint in multisample configs
                if (isSingleSample)
                    m_cases.push_back(FilterCase(tex0, tcu::Vec2(-1.25f, -1.2f), tcu::Vec2(1.2f, 1.25f),
                                                 tcu::Vec2(-0.5f, float(numLayers) + 0.5f)));
                else
                    m_cases.push_back(FilterCase(tex0, tcu::Vec2(-1.19f, -1.3f), tcu::Vec2(1.1f, 1.35f),
                                                 tcu::Vec2(-0.5f, float(numLayers) + 0.5f)));

                m_cases.push_back(FilterCase(tex0, tcu::Vec2(0.8f, 0.8f), tcu::Vec2(1.25f, 1.20f),
                                             tcu::Vec2(-0.5f, float(numLayers) + 0.5f))); // magnification
                m_cases.push_back(FilterCase(tex1, tcu::Vec2(-1.19f, -1.3f), tcu::Vec2(1.1f, 1.35f),
                                             tcu::Vec2(float(numLayers) + 0.5f, -0.5f))); // minification
                m_cases.push_back(FilterCase(tex1, tcu::Vec2(-1.2f, -1.1f), tcu::Vec2(-0.8f, -0.8f),
                                             tcu::Vec2(float(numLayers) + 0.5f, -0.5f))); // magnification

                // Layer rounding - only in single-sample configs as multisample configs may produce smooth transition at the middle.
                if (isSingleSample && (numLayers > 1))
                    m_cases.push_back(FilterCase(tex0, tcu::Vec2(-2.0f, -1.5f), tcu::Vec2(-0.1f, 0.9f),
                                                 tcu::Vec2(1.50001f, 1.49999f)));
            }
        }

        m_caseNdx = 0;
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    }
    catch (...)
    {
        // Clean up to save memory.
        TextureCubeArrayFilteringCase::deinit();
        throw;
    }
}

void TextureCubeArrayFilteringCase::deinit(void)
{
    delete m_gradientTex;
    delete m_gridTex;

    m_gradientTex = DE_NULL;
    m_gridTex     = DE_NULL;

    m_renderer.clear();
    m_cases.clear();

    if (!isContextTypeES(m_context.getRenderContext().getType()))
        m_context.getRenderContext().getFunctions().disable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
}

TextureCubeArrayFilteringCase::IterateResult TextureCubeArrayFilteringCase::iterate(void)
{
    TestLog &log                        = m_testCtx.getLog();
    const glu::RenderContext &renderCtx = m_context.getRenderContext();
    const glw::Functions &gl            = renderCtx.getFunctions();
    const int viewportSize              = 28;
    const uint32_t randomSeed =
        deStringHash(getName()) ^ deInt32Hash(m_caseNdx) ^ m_testCtx.getCommandLine().getBaseSeed();
    const RandomViewport viewport(m_context.getRenderTarget(), viewportSize, viewportSize, randomSeed);
    const FilterCase &curCase            = m_cases[m_caseNdx];
    const tcu::TextureFormat texFmt      = curCase.texture->getRefTexture().getFormat();
    const tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(texFmt);
    const tcu::ScopedLogSection section(m_testCtx.getLog(), string("Test") + de::toString(m_caseNdx),
                                        string("Test ") + de::toString(m_caseNdx));
    ReferenceParams refParams(TEXTURETYPE_CUBE_ARRAY);

    if (viewport.width < viewportSize || viewport.height < viewportSize)
        throw tcu::NotSupportedError("Render target too small", "", __FILE__, __LINE__);

    // Params for reference computation.
    refParams.sampler                 = glu::mapGLSampler(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, m_minFilter, m_magFilter);
    refParams.sampler.seamlessCubeMap = true;
    refParams.samplerType             = getSamplerType(texFmt);
    refParams.colorBias               = fmtInfo.lookupBias;
    refParams.colorScale              = fmtInfo.lookupScale;
    refParams.lodMode                 = LODMODE_EXACT;

    gl.bindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, curCase.texture->getGLTexture());
    gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, m_minFilter);
    gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, m_magFilter);
    gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, m_wrapS);
    gl.texParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, m_wrapT);

    gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);

    m_testCtx.getLog() << TestLog::Message << "Coordinates: " << curCase.bottomLeft << " -> " << curCase.topRight
                       << TestLog::EndMessage;

    for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
    {
        const tcu::CubeFace face = tcu::CubeFace(faceNdx);
        tcu::Surface result(viewport.width, viewport.height);
        vector<float> texCoord;

        computeQuadTexCoordCubeArray(texCoord, face, curCase.bottomLeft, curCase.topRight, curCase.layerRange);

        log << TestLog::Message << "Face " << getFaceDesc(face) << TestLog::EndMessage;

        log << TestLog::Message << "Texture coordinates:" << TestLog::EndMessage;

        logCubeArrayTexCoords(log, texCoord);

        m_renderer.renderQuad(0, &texCoord[0], refParams);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");

        glu::readPixels(renderCtx, viewport.x, viewport.y, result.getAccess());
        GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels");

        {
            const bool isNearestOnly           = m_minFilter == GL_NEAREST && m_magFilter == GL_NEAREST;
            const tcu::PixelFormat pixelFormat = renderCtx.getRenderTarget().getPixelFormat();
            const tcu::IVec4 coordBits         = tcu::IVec4(10);
            const tcu::IVec4 colorBits         = max(getBitsVec(pixelFormat) - (isNearestOnly ? 1 : 2),
                                                     tcu::IVec4(0)); // 1 inaccurate bit if nearest only, 2 otherwise
            tcu::LodPrecision lodPrecision;
            tcu::LookupPrecision lookupPrecision;

            lodPrecision.derivateBits      = 10;
            lodPrecision.lodBits           = 5;
            lookupPrecision.colorThreshold = tcu::computeFixedPointThreshold(colorBits) / refParams.colorScale;
            lookupPrecision.coordBits      = coordBits.toWidth<3>();
            lookupPrecision.uvwBits        = tcu::IVec3(6);
            lookupPrecision.colorMask      = getCompareMask(pixelFormat);

            const bool isHighQuality =
                verifyTextureResult(m_testCtx, result.getAccess(), curCase.texture->getRefTexture(), &texCoord[0],
                                    refParams, lookupPrecision, coordBits, lodPrecision, pixelFormat);

            if (!isHighQuality)
            {
                // Evaluate against lower precision requirements.
                lodPrecision.lodBits    = 4;
                lookupPrecision.uvwBits = tcu::IVec3(4);

                m_testCtx.getLog() << TestLog::Message
                                   << "Warning: Verification against high precision requirements failed, trying with "
                                      "lower requirements."
                                   << TestLog::EndMessage;

                const bool isOk =
                    verifyTextureResult(m_testCtx, result.getAccess(), curCase.texture->getRefTexture(), &texCoord[0],
                                        refParams, lookupPrecision, coordBits, lodPrecision, pixelFormat);

                if (!isOk)
                {
                    m_testCtx.getLog()
                        << TestLog::Message
                        << "ERROR: Verification against low precision requirements failed, failing test case."
                        << TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
                }
                else if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Low-quality filtering result");
            }
        }
    }

    m_caseNdx += 1;
    return m_caseNdx < (int)m_cases.size() ? CONTINUE : STOP;
}

TextureFilteringTests::TextureFilteringTests(Context &context)
    : TestCaseGroup(context, "filtering", "Texture Filtering Tests")
{
}

TextureFilteringTests::~TextureFilteringTests(void)
{
}

void TextureFilteringTests::init(void)
{
    static const struct
    {
        const char *name;
        uint32_t mode;
    } wrapModes[] = {{"clamp", GL_CLAMP_TO_EDGE}, {"repeat", GL_REPEAT}, {"mirror", GL_MIRRORED_REPEAT}};

    static const struct
    {
        const char *name;
        uint32_t mode;
    } minFilterModes[] = {{"nearest", GL_NEAREST},
                          {"linear", GL_LINEAR},
                          {"nearest_mipmap_nearest", GL_NEAREST_MIPMAP_NEAREST},
                          {"linear_mipmap_nearest", GL_LINEAR_MIPMAP_NEAREST},
                          {"nearest_mipmap_linear", GL_NEAREST_MIPMAP_LINEAR},
                          {"linear_mipmap_linear", GL_LINEAR_MIPMAP_LINEAR}};

    static const struct
    {
        const char *name;
        uint32_t mode;
    } magFilterModes[] = {{"nearest", GL_NEAREST}, {"linear", GL_LINEAR}};

    static const struct
    {
        int size;
        int depth;
    } sizesCubeArray[] = {{8, 6}, {64, 12}, {128, 12}, {7, 12}, {63, 18}};

    static const struct
    {
        const char *name;
        uint32_t format;
    } filterableFormatsByType[] = {{"rgba16f", GL_RGBA16F},
                                   {"r11f_g11f_b10f", GL_R11F_G11F_B10F},
                                   {"rgb9_e5", GL_RGB9_E5},
                                   {"rgba8", GL_RGBA8},
                                   {"rgba8_snorm", GL_RGBA8_SNORM},
                                   {"rgb565", GL_RGB565},
                                   {"rgba4", GL_RGBA4},
                                   {"rgb5_a1", GL_RGB5_A1},
                                   {"sr8", GL_SR8_EXT},
                                   {"srg8", GL_SRG8_EXT},
                                   {"srgb8_alpha8", GL_SRGB8_ALPHA8},
                                   {"rgb10_a2", GL_RGB10_A2}};

    // Cube map array texture filtering.
    {
        tcu::TestCaseGroup *const groupCubeArray =
            new tcu::TestCaseGroup(m_testCtx, "cube_array", "Cube Map Array Texture Filtering");
        addChild(groupCubeArray);

        // Formats.
        {
            tcu::TestCaseGroup *const formatsGroup =
                new tcu::TestCaseGroup(m_testCtx, "formats", "Cube Map Array Texture Formats");
            groupCubeArray->addChild(formatsGroup);

            for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(filterableFormatsByType); fmtNdx++)
            {
                for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
                {
                    const uint32_t minFilter = minFilterModes[filterNdx].mode;
                    const char *filterName   = minFilterModes[filterNdx].name;
                    const uint32_t format    = filterableFormatsByType[fmtNdx].format;
                    const char *formatName   = filterableFormatsByType[fmtNdx].name;
                    const bool isMipmap      = minFilter != GL_NEAREST && minFilter != GL_LINEAR;
                    const uint32_t magFilter = isMipmap ? GL_LINEAR : minFilter;
                    const string name        = string(formatName) + "_" + filterName;
                    const uint32_t wrapS     = GL_REPEAT;
                    const uint32_t wrapT     = GL_REPEAT;
                    const int size           = 64;
                    const int depth          = 12;

                    formatsGroup->addChild(new TextureCubeArrayFilteringCase(
                        m_context, name.c_str(), "", minFilter, magFilter, wrapS, wrapT, format, size, depth));
                }
            }
        }

        // Sizes.
        {
            tcu::TestCaseGroup *const sizesGroup = new tcu::TestCaseGroup(m_testCtx, "sizes", "Texture Sizes");
            groupCubeArray->addChild(sizesGroup);

            for (int sizeNdx = 0; sizeNdx < DE_LENGTH_OF_ARRAY(sizesCubeArray); sizeNdx++)
            {
                for (int filterNdx = 0; filterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); filterNdx++)
                {
                    const uint32_t minFilter = minFilterModes[filterNdx].mode;
                    const char *filterName   = minFilterModes[filterNdx].name;
                    const uint32_t format    = GL_RGBA8;
                    const bool isMipmap      = minFilter != GL_NEAREST && minFilter != GL_LINEAR;
                    const uint32_t magFilter = isMipmap ? GL_LINEAR : minFilter;
                    const uint32_t wrapS     = GL_REPEAT;
                    const uint32_t wrapT     = GL_REPEAT;
                    const int size           = sizesCubeArray[sizeNdx].size;
                    const int depth          = sizesCubeArray[sizeNdx].depth;
                    const string name =
                        de::toString(size) + "x" + de::toString(size) + "x" + de::toString(depth) + "_" + filterName;

                    sizesGroup->addChild(new TextureCubeArrayFilteringCase(
                        m_context, name.c_str(), "", minFilter, magFilter, wrapS, wrapT, format, size, depth));
                }
            }
        }

        // Wrap modes.
        {
            tcu::TestCaseGroup *const combinationsGroup =
                new tcu::TestCaseGroup(m_testCtx, "combinations", "Filter and wrap mode combinations");
            groupCubeArray->addChild(combinationsGroup);

            for (int minFilterNdx = 0; minFilterNdx < DE_LENGTH_OF_ARRAY(minFilterModes); minFilterNdx++)
            {
                for (int magFilterNdx = 0; magFilterNdx < DE_LENGTH_OF_ARRAY(magFilterModes); magFilterNdx++)
                {
                    for (int wrapSNdx = 0; wrapSNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapSNdx++)
                    {
                        for (int wrapTNdx = 0; wrapTNdx < DE_LENGTH_OF_ARRAY(wrapModes); wrapTNdx++)
                        {
                            const uint32_t minFilter = minFilterModes[minFilterNdx].mode;
                            const uint32_t magFilter = magFilterModes[magFilterNdx].mode;
                            const uint32_t format    = GL_RGBA8;
                            const uint32_t wrapS     = wrapModes[wrapSNdx].mode;
                            const uint32_t wrapT     = wrapModes[wrapTNdx].mode;
                            const int size           = 63;
                            const int depth          = 12;
                            const string name        = string(minFilterModes[minFilterNdx].name) + "_" +
                                                magFilterModes[magFilterNdx].name + "_" + wrapModes[wrapSNdx].name +
                                                "_" + wrapModes[wrapTNdx].name;

                            combinationsGroup->addChild(new TextureCubeArrayFilteringCase(
                                m_context, name.c_str(), "", minFilter, magFilter, wrapS, wrapT, format, size, depth));
                        }
                    }
                }
            }
        }

        // Cases with no visible cube edges.
        {
            tcu::TestCaseGroup *const onlyFaceInteriorGroup =
                new tcu::TestCaseGroup(m_testCtx, "no_edges_visible", "Don't sample anywhere near a face's edges");
            groupCubeArray->addChild(onlyFaceInteriorGroup);

            for (int isLinearI = 0; isLinearI <= 1; isLinearI++)
            {
                const bool isLinear   = isLinearI != 0;
                const uint32_t filter = isLinear ? GL_LINEAR : GL_NEAREST;

                onlyFaceInteriorGroup->addChild(
                    new TextureCubeArrayFilteringCase(m_context, isLinear ? "linear" : "nearest", "", filter, filter,
                                                      GL_REPEAT, GL_REPEAT, GL_RGBA8, 63, 12, true));
            }
        }
    }
}

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