/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2016 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 Negative Advanced Blend Equation Tests
 *//*--------------------------------------------------------------------*/

#include "es31fNegativeAdvancedBlendEquationTests.hpp"

#include "gluShaderProgram.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace NegativeTestShared
{
namespace
{

enum BlendEquation
{
    BLEND_EQUATION_MULTIPLY = 0,
    BLEND_EQUATION_SCREEN,
    BLEND_EQUATION_OVERLAY,
    BLEND_EQUATION_DARKEN,
    BLEND_EQUATION_LIGHTEN,
    BLEND_EQUATION_COLORDODGE,
    BLEND_EQUATION_COLORBURN,
    BLEND_EQUATION_HARDLIGHT,
    BLEND_EQUATION_SOFTLIGHT,
    BLEND_EQUATION_DIFFERENCE,
    BLEND_EQUATION_EXCLUSION,
    BLEND_EQUATION_HSL_HUE,
    BLEND_EQUATION_HSL_SATURATION,
    BLEND_EQUATION_HSL_COLOR,
    BLEND_EQUATION_HSL_LUMINOSITY,
    BLEND_EQUATION_ALL_EQUATIONS,

    BLEND_EQUATION_LAST
};

static const BlendEquation s_equations[] = {
    BLEND_EQUATION_MULTIPLY,       BLEND_EQUATION_SCREEN,     BLEND_EQUATION_OVERLAY,       BLEND_EQUATION_DARKEN,
    BLEND_EQUATION_LIGHTEN,        BLEND_EQUATION_COLORDODGE, BLEND_EQUATION_COLORBURN,     BLEND_EQUATION_HARDLIGHT,
    BLEND_EQUATION_SOFTLIGHT,      BLEND_EQUATION_DIFFERENCE, BLEND_EQUATION_EXCLUSION,     BLEND_EQUATION_HSL_HUE,
    BLEND_EQUATION_HSL_SATURATION, BLEND_EQUATION_HSL_COLOR,  BLEND_EQUATION_HSL_LUMINOSITY};

std::string getShaderLayoutEquation(BlendEquation equation)
{
    switch (equation)
    {
    case BLEND_EQUATION_MULTIPLY:
        return "blend_support_multiply";
    case BLEND_EQUATION_SCREEN:
        return "blend_support_screen";
    case BLEND_EQUATION_OVERLAY:
        return "blend_support_overlay";
    case BLEND_EQUATION_DARKEN:
        return "blend_support_darken";
    case BLEND_EQUATION_LIGHTEN:
        return "blend_support_lighten";
    case BLEND_EQUATION_COLORDODGE:
        return "blend_support_colordodge";
    case BLEND_EQUATION_COLORBURN:
        return "blend_support_colorburn";
    case BLEND_EQUATION_HARDLIGHT:
        return "blend_support_hardlight";
    case BLEND_EQUATION_SOFTLIGHT:
        return "blend_support_softlight";
    case BLEND_EQUATION_DIFFERENCE:
        return "blend_support_difference";
    case BLEND_EQUATION_EXCLUSION:
        return "blend_support_exclusion";
    case BLEND_EQUATION_HSL_HUE:
        return "blend_support_hsl_hue";
    case BLEND_EQUATION_HSL_SATURATION:
        return "blend_support_hsl_saturation";
    case BLEND_EQUATION_HSL_COLOR:
        return "blend_support_hsl_color";
    case BLEND_EQUATION_HSL_LUMINOSITY:
        return "blend_support_hsl_luminosity";
    case BLEND_EQUATION_ALL_EQUATIONS:
        return "blend_support_all_equations";
    default:
        DE_FATAL("Equation not supported.");
    }
    return "";
}

glw::GLenum getEquation(BlendEquation equation)
{
    switch (equation)
    {
    case BLEND_EQUATION_MULTIPLY:
        return GL_MULTIPLY;
    case BLEND_EQUATION_SCREEN:
        return GL_SCREEN;
    case BLEND_EQUATION_OVERLAY:
        return GL_OVERLAY;
    case BLEND_EQUATION_DARKEN:
        return GL_DARKEN;
    case BLEND_EQUATION_LIGHTEN:
        return GL_LIGHTEN;
    case BLEND_EQUATION_COLORDODGE:
        return GL_COLORDODGE;
    case BLEND_EQUATION_COLORBURN:
        return GL_COLORBURN;
    case BLEND_EQUATION_HARDLIGHT:
        return GL_HARDLIGHT;
    case BLEND_EQUATION_SOFTLIGHT:
        return GL_SOFTLIGHT;
    case BLEND_EQUATION_DIFFERENCE:
        return GL_DIFFERENCE;
    case BLEND_EQUATION_EXCLUSION:
        return GL_EXCLUSION;
    case BLEND_EQUATION_HSL_HUE:
        return GL_HSL_HUE;
    case BLEND_EQUATION_HSL_SATURATION:
        return GL_HSL_SATURATION;
    case BLEND_EQUATION_HSL_COLOR:
        return GL_HSL_COLOR;
    case BLEND_EQUATION_HSL_LUMINOSITY:
        return GL_HSL_LUMINOSITY;
    default:
        DE_FATAL("Equation not supported.");
    }
    return DE_NULL;
}

std::string generateVertexShaderSource(NegativeTestContext &ctx)
{
    const bool supportsES32        = contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
    const glu::GLSLVersion version = supportsES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
    std::ostringstream source;

    source << glu::getGLSLVersionDeclaration(version) << "\n"
           << "void main ()\n"
           << "{\n"
           << "    gl_Position = vec4(0.0);\n"
           << "}\n";

    return source.str();
}

std::string generateFragmentShaderSource(NegativeTestContext &ctx, BlendEquation equation)
{
    const bool supportsES32        = contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
    const glu::GLSLVersion version = supportsES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
    std::ostringstream source;

    source << glu::getGLSLVersionDeclaration(version) << "\n"
           << (supportsES32 ? "" : "#extension GL_KHR_blend_equation_advanced : enable\n") << "layout("
           << getShaderLayoutEquation(equation) << ") out;\n"
           << "layout(location=0) out mediump vec4 o_color;\n"
           << "void main ()\n"
           << "{\n"
           << "    o_color = vec4(0);\n"
           << "}\n";

    return source.str();
}

glu::ProgramSources generateProgramSources(NegativeTestContext &ctx, BlendEquation equation)
{
    return glu::ProgramSources() << glu::VertexSource(generateVertexShaderSource(ctx))
                                 << glu::FragmentSource(generateFragmentShaderSource(ctx, equation));
}

void blend_qualifier_mismatch(NegativeTestContext &ctx)
{
    TCU_CHECK_AND_THROW(NotSupportedError,
                        ctx.isExtensionSupported("GL_KHR_blend_equation_advanced") ||
                            contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2)),
                        "This test requires support for the extension GL_KHR_blend_equation_advanced or context "
                        "version 3.2 or higher.");

    glw::GLuint vao = 0;
    bool isES       = glu::isContextTypeES(ctx.getRenderContext().getType());
    if (!isES)
    {
        ctx.glGenVertexArrays(1, &vao);
        ctx.glBindVertexArray(vao);
    }

    ctx.beginSection("GL_INVALID_OPERATION is generated if blending is enabled, and the blend qualifier is different "
                     "from blend_support_all_equations and does not match the blend equation.");
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_equations); ++ndx)
    {
        glu::ShaderProgram program(ctx.getRenderContext(), generateProgramSources(ctx, s_equations[ndx]));

        ctx.getLog() << program;
        TCU_CHECK(program.isOk());

        ctx.glUseProgram(program.getProgram());
        ctx.expectError(GL_NO_ERROR);

        for (int ndx2 = 0; ndx2 < DE_LENGTH_OF_ARRAY(s_equations); ++ndx2)
        {
            if (s_equations[ndx] == s_equations[ndx2])
                continue;

            ctx.glEnable(GL_BLEND);
            ctx.glBlendEquation(getEquation(s_equations[ndx2]));
            ctx.expectError(GL_NO_ERROR);
            ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
            ctx.expectError(GL_INVALID_OPERATION);

            ctx.glDisable(GL_BLEND);
            ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
            ctx.expectError(GL_NO_ERROR);
        }
    }
    ctx.endSection();

    if (!isES)
        ctx.glDeleteVertexArrays(1, &vao);
}

void attachment_advanced_equation(NegativeTestContext &ctx)
{
    TCU_CHECK_AND_THROW(NotSupportedError,
                        ctx.isExtensionSupported("GL_KHR_blend_equation_advanced") ||
                            contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2)),
                        "This test requires support for the extension GL_KHR_blend_equation_advanced or context "
                        "version 3.2 or higher.");

    glw::GLuint vao                 = 0;
    glw::GLuint fbo                 = 0x1234;
    glw::GLuint texture             = 0x1234;
    const glw::GLenum attachments[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
    const bool isES                 = glu::isContextTypeES(ctx.getRenderContext().getType());

    if (!isES)
    {
        ctx.glGenVertexArrays(1, &vao);
        ctx.glBindVertexArray(vao);
    }

    ctx.glGenTextures(1, &texture);
    ctx.glBindTexture(GL_TEXTURE_2D, texture);
    ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    ctx.glGenFramebuffers(1, &fbo);
    ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ctx.expectError(GL_NO_ERROR);
    ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);

    ctx.beginSection(
        "GL_INVALID_OPERATION is generated if blending is enabled, advanced equations are used, and the draw buffer "
        "for other color outputs is not NONE unless NVX_blend_equation_advanced_multi_draw_buffers is supported.");
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_equations); ++ndx)
    {
        glu::ShaderProgram program(ctx.getRenderContext(), generateProgramSources(ctx, s_equations[ndx]));
        ctx.getLog() << program;
        TCU_CHECK(program.isOk());

        ctx.glUseProgram(program.getProgram());
        ctx.glEnable(GL_BLEND);
        ctx.glDrawBuffers(2, attachments);
        ctx.expectError(GL_NO_ERROR);
        ctx.glBlendEquation(getEquation(s_equations[ndx]));
        ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
        if (ctx.isExtensionSupported("GL_NVX_blend_equation_advanced_multi_draw_buffers"))
            ctx.expectError(GL_NO_ERROR);
        else
            ctx.expectError(GL_INVALID_OPERATION);
    }
    ctx.endSection();

    ctx.beginSection("GL_NO_ERROR is generated if no advanced blend equations are used.");
    ctx.glBlendEquation(GL_FUNC_ADD);
    ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
    ctx.expectError(GL_NO_ERROR);
    ctx.endSection();

    ctx.beginSection("GL_NO_ERROR is generated if blending is disabled.");
    ctx.glDisable(GL_BLEND);
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_equations); ++ndx)
    {
        ctx.glBlendEquation(getEquation(s_equations[ndx]));
        ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
        ctx.expectError(GL_NO_ERROR);
    }
    ctx.endSection();

    if (!isES)
        ctx.glDeleteVertexArrays(1, &vao);

    ctx.glDeleteFramebuffers(1, &fbo);
    ctx.glDeleteTextures(1, &texture);
}

} // namespace

std::vector<FunctionContainer> getNegativeAdvancedBlendEquationTestFunctions(void)
{
    const FunctionContainer funcs[] = {
        {blend_qualifier_mismatch, "blend_qualifier_mismatch", "Test blend qualifier mismatch."},
        {attachment_advanced_equation, "attachment_advanced_equation", "Test draw buffer for other color outputs."},
    };

    return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
}

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