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

#include "es3fTextureSwizzleTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTexture.hpp"
#include "gluRenderContext.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "deString.h"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

namespace deqp
{
namespace gles3
{
namespace Functional
{

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

static int swizzle(const tcu::RGBA &c, uint32_t swz)
{
    switch (swz)
    {
    case GL_RED:
        return c.getRed();
    case GL_GREEN:
        return c.getGreen();
    case GL_BLUE:
        return c.getBlue();
    case GL_ALPHA:
        return c.getAlpha();
    case GL_ZERO:
        return 0;
    case GL_ONE:
        return (1 << 8) - 1;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

static void swizzle(tcu::Surface &surface, uint32_t swzR, uint32_t swzG, uint32_t swzB, uint32_t swzA)
{
    for (int y = 0; y < surface.getHeight(); y++)
    {
        for (int x = 0; x < surface.getWidth(); x++)
        {
            tcu::RGBA p = surface.getPixel(x, y);
            surface.setPixel(x, y, tcu::RGBA(swizzle(p, swzR), swizzle(p, swzG), swizzle(p, swzB), swizzle(p, swzA)));
        }
    }
}

class Texture2DSwizzleCase : public TestCase
{
public:
    Texture2DSwizzleCase(Context &context, const char *name, const char *description, uint32_t internalFormat,
                         uint32_t format, uint32_t dataType, uint32_t swizzleR, uint32_t swizzleG, uint32_t swizzleB,
                         uint32_t swizzleA);
    ~Texture2DSwizzleCase(void);

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

private:
    Texture2DSwizzleCase(const Texture2DSwizzleCase &other);
    Texture2DSwizzleCase &operator=(const Texture2DSwizzleCase &other);

    uint32_t m_internalFormat;
    uint32_t m_format;
    uint32_t m_dataType;
    uint32_t m_swizzleR;
    uint32_t m_swizzleG;
    uint32_t m_swizzleB;
    uint32_t m_swizzleA;

    glu::Texture2D *m_texture;
    TextureRenderer m_renderer;
};

Texture2DSwizzleCase::Texture2DSwizzleCase(Context &context, const char *name, const char *description,
                                           uint32_t internalFormat, uint32_t format, uint32_t dataType,
                                           uint32_t swizzleR, uint32_t swizzleG, uint32_t swizzleB, uint32_t swizzleA)
    : TestCase(context, name, description)
    , m_internalFormat(internalFormat)
    , m_format(format)
    , m_dataType(dataType)
    , m_swizzleR(swizzleR)
    , m_swizzleG(swizzleG)
    , m_swizzleB(swizzleB)
    , m_swizzleA(swizzleA)
    , m_texture(DE_NULL)
    , m_renderer(context.getRenderContext(), context.getTestContext().getLog(), glu::GLSL_VERSION_300_ES,
                 glu::PRECISION_HIGHP)
{
}

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

void Texture2DSwizzleCase::init(void)
{
    int width  = de::min(128, m_context.getRenderContext().getRenderTarget().getWidth());
    int height = de::min(128, m_context.getRenderContext().getRenderTarget().getHeight());

    m_texture = (m_internalFormat == m_format) ?
                    new glu::Texture2D(m_context.getRenderContext(), m_format, m_dataType, width, height) :
                    new glu::Texture2D(m_context.getRenderContext(), m_internalFormat, width, height);

    tcu::TextureFormatInfo spec = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());

    // Fill level 0.
    m_texture->getRefTexture().allocLevel(0);
    tcu::fillWithComponentGradients(m_texture->getRefTexture().getLevel(0), spec.valueMin, spec.valueMax);
}

void Texture2DSwizzleCase::deinit(void)
{
    delete m_texture;
    m_texture = DE_NULL;

    m_renderer.clear();
}

Texture2DSwizzleCase::IterateResult Texture2DSwizzleCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();
    RandomViewport viewport(m_context.getRenderContext().getRenderTarget(), m_texture->getRefTexture().getWidth(),
                            m_texture->getRefTexture().getHeight(), deStringHash(getName()));
    tcu::Surface renderedFrame(viewport.width, viewport.height);
    tcu::Surface referenceFrame(viewport.width, viewport.height);
    tcu::RGBA threshold = m_context.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(1, 1, 1, 1);
    vector<float> texCoord;
    ReferenceParams renderParams(TEXTURETYPE_2D);
    tcu::TextureFormatInfo spec = tcu::getTextureFormatInfo(m_texture->getRefTexture().getFormat());

    renderParams.samplerType = getSamplerType(m_texture->getRefTexture().getFormat());
    renderParams.sampler     = tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
                                            tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST);
    renderParams.colorScale  = spec.lookupScale;
    renderParams.colorBias   = spec.lookupBias;

    computeQuadTexCoord2D(texCoord, tcu::Vec2(0.0f, 0.0f), tcu::Vec2(1.0f, 1.0f));

    // Setup base viewport.
    gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);

    // Upload texture data to GL.
    m_texture->upload();

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

    // Setup nearest neighbor filtering and clamp-to-edge.
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Setup texture swizzle.
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, m_swizzleR);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, m_swizzleG);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, m_swizzleB);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, m_swizzleA);

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

    // Draw.
    m_renderer.renderQuad(0, &texCoord[0], renderParams);
    glu::readPixels(m_context.getRenderContext(), viewport.x, viewport.y, renderedFrame.getAccess());

    // Compute reference
    {
        const tcu::PixelFormat pixelFormat = m_context.getRenderTarget().getPixelFormat();

        // Do initial rendering to RGBA8 in order to keep alpha
        sampleTexture(tcu::SurfaceAccess(referenceFrame, tcu::PixelFormat(8, 8, 8, 8)), m_texture->getRefTexture(),
                      &texCoord[0], renderParams);

        // Swizzle channels
        swizzle(referenceFrame, m_swizzleR, m_swizzleG, m_swizzleB, m_swizzleA);

        // Convert to destination format
        if (pixelFormat != tcu::PixelFormat(8, 8, 8, 8))
        {
            for (int y = 0; y < referenceFrame.getHeight(); y++)
            {
                for (int x = 0; x < referenceFrame.getWidth(); x++)
                {
                    tcu::RGBA p = referenceFrame.getPixel(x, y);
                    referenceFrame.setPixel(x, y, pixelFormat.convertColor(p));
                }
            }
        }
    }

    // Compare and log.
    bool isOk = compareImages(log, referenceFrame, renderedFrame, threshold);

    m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                            isOk ? "Pass" : "Image comparison failed");

    return STOP;
}

TextureSwizzleTests::TextureSwizzleTests(Context &context) : TestCaseGroup(context, "swizzle", "Texture Swizzle Tests")
{
}

TextureSwizzleTests::~TextureSwizzleTests(void)
{
}

void TextureSwizzleTests::init(void)
{
    static const struct
    {
        const char *name;
        uint32_t internalFormat;
        uint32_t format;
        uint32_t dataType;
    } formats[] = {{"alpha", GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE},
                   {"luminance", GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE},
                   {"luminance_alpha", GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE},
                   {"red", GL_R8, GL_RED, GL_UNSIGNED_BYTE},
                   {"rg", GL_RG8, GL_RG, GL_UNSIGNED_BYTE},
                   {"rgb", GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE},
                   {"rgba", GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}};

    static const struct
    {
        const char *name;
        uint32_t channel;
    } channels[] = {{"r", GL_TEXTURE_SWIZZLE_R},
                    {"g", GL_TEXTURE_SWIZZLE_G},
                    {"b", GL_TEXTURE_SWIZZLE_B},
                    {"a", GL_TEXTURE_SWIZZLE_A}};

    static const struct
    {
        const char *name;
        uint32_t swizzle;
    } swizzles[] = {{"red", GL_RED},     {"green", GL_GREEN}, {"blue", GL_BLUE},
                    {"alpha", GL_ALPHA}, {"zero", GL_ZERO},   {"one", GL_ONE}};

    static const struct
    {
        const char *name;
        uint32_t swzR;
        uint32_t swzG;
        uint32_t swzB;
        uint32_t swzA;
    } swizzleCases[] = {{"all_red", GL_RED, GL_RED, GL_RED, GL_RED},
                        {"all_green", GL_GREEN, GL_GREEN, GL_GREEN, GL_GREEN},
                        {"all_blue", GL_BLUE, GL_BLUE, GL_BLUE, GL_BLUE},
                        {"all_alpha", GL_ALPHA, GL_ALPHA, GL_ALPHA, GL_ALPHA},
                        {"all_zero", GL_ZERO, GL_ZERO, GL_ZERO, GL_ZERO},
                        {"all_one", GL_ONE, GL_ONE, GL_ONE, GL_ONE},
                        {"bgra", GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA},
                        {"abgr", GL_ALPHA, GL_BLUE, GL_GREEN, GL_RED},
                        {"one_one_red_green", GL_ONE, GL_ONE, GL_RED, GL_GREEN}};

    static const uint32_t defaultSwizzles[] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};

    // All swizzles applied to each channel.
    tcu::TestCaseGroup *singleChannelGroup =
        new tcu::TestCaseGroup(m_testCtx, "single_channel", "Single-channel swizzle");
    addChild(singleChannelGroup);
    for (int chanNdx = 0; chanNdx < DE_LENGTH_OF_ARRAY(channels); chanNdx++)
    {
        for (int swzNdx = 0; swzNdx < DE_LENGTH_OF_ARRAY(swizzles); swzNdx++)
        {
            if (swizzles[swzNdx].swizzle == defaultSwizzles[chanNdx])
                continue; // No need to test default case.

            string name   = string(channels[chanNdx].name) + "_" + swizzles[swzNdx].name;
            uint32_t swz  = swizzles[swzNdx].swizzle;
            uint32_t swzR = (chanNdx == 0) ? swz : defaultSwizzles[0];
            uint32_t swzG = (chanNdx == 1) ? swz : defaultSwizzles[1];
            uint32_t swzB = (chanNdx == 2) ? swz : defaultSwizzles[2];
            uint32_t swzA = (chanNdx == 3) ? swz : defaultSwizzles[3];

            singleChannelGroup->addChild(new Texture2DSwizzleCase(m_context, name.c_str(), "Single-channel swizzle",
                                                                  GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, swzR, swzG, swzB,
                                                                  swzA));
        }
    }

    // Swizzles for all formats.
    tcu::TestCaseGroup *multiChannelGroup = new tcu::TestCaseGroup(m_testCtx, "multi_channel", "Multi-channel swizzle");
    addChild(multiChannelGroup);
    for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(formats); fmtNdx++)
    {
        for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(swizzleCases); caseNdx++)
        {
            string name        = string(formats[fmtNdx].name) + "_" + swizzleCases[caseNdx].name;
            uint32_t swzR      = swizzleCases[caseNdx].swzR;
            uint32_t swzG      = swizzleCases[caseNdx].swzG;
            uint32_t swzB      = swizzleCases[caseNdx].swzB;
            uint32_t swzA      = swizzleCases[caseNdx].swzA;
            uint32_t intFormat = formats[fmtNdx].internalFormat;
            uint32_t format    = formats[fmtNdx].format;
            uint32_t dataType  = formats[fmtNdx].dataType;

            multiChannelGroup->addChild(new Texture2DSwizzleCase(m_context, name.c_str(), "Multi-channel swizzle",
                                                                 intFormat, format, dataType, swzR, swzG, swzB, swzA));
        }
    }
}

} // namespace Functional
} // namespace gles3
} // namespace deqp
