//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// GLES1Renderer.cpp: Implements the GLES1Renderer renderer.

#include "libANGLE/GLES1Renderer.h"

#include <string.h>
#include <iterator>
#include <sstream>
#include <vector>

#include "libANGLE/Context.h"
#include "libANGLE/Program.h"
#include "libANGLE/ResourceManager.h"
#include "libANGLE/Shader.h"
#include "libANGLE/State.h"
#include "libANGLE/renderer/ContextImpl.h"

namespace
{
#include "libANGLE/GLES1Shaders.inc"
}  // anonymous namespace

namespace gl
{

GLES1Renderer::GLES1Renderer() : mRendererProgramInitialized(false)
{
}

void GLES1Renderer::onDestroy(Context *context, State *state)
{
    if (mRendererProgramInitialized)
    {
        state->setProgram(context, 0);

        mShaderPrograms->deleteProgram(context, mProgramState.program);
        mShaderPrograms->release(context);
        mShaderPrograms             = nullptr;
        mRendererProgramInitialized = false;
    }
}

GLES1Renderer::~GLES1Renderer() = default;

Error GLES1Renderer::prepareForDraw(Context *context, State *glState)
{
    ANGLE_TRY(initializeRendererProgram(context, glState));

    const GLES1State &gles1State = glState->gles1();

    Program *programObject = getProgram(mProgramState.program);

    GLES1UniformBuffers &uniformBuffers = mUniformBuffers;

    if (!gles1State.isClientStateEnabled(ClientVertexArrayType::Normal))
    {
        const angle::Vector3 normal = gles1State.getCurrentNormal();
        context->vertexAttrib3f(kNormalAttribIndex, normal.x(), normal.y(), normal.z());
    }

    if (!gles1State.isClientStateEnabled(ClientVertexArrayType::Color))
    {
        const ColorF color = gles1State.getCurrentColor();
        context->vertexAttrib4f(kColorAttribIndex, color.red, color.green, color.blue, color.alpha);
    }

    if (!gles1State.isClientStateEnabled(ClientVertexArrayType::PointSize))
    {
        GLfloat pointSize = gles1State.mPointParameters.pointSize;
        context->vertexAttrib1f(kPointSizeAttribIndex, pointSize);
    }

    for (int i = 0; i < kTexUnitCount; i++)
    {
        if (!gles1State.mTexCoordArrayEnabled[i])
        {
            const TextureCoordF texcoord = gles1State.getCurrentTextureCoords(i);
            context->vertexAttrib4f(kTextureCoordAttribIndexBase + i, texcoord.s, texcoord.t,
                                    texcoord.r, texcoord.q);
        }
    }

    // Matrices
    {
        angle::Mat4 proj = gles1State.mProjectionMatrices.back();
        setUniformMatrix4fv(programObject, mProgramState.projMatrixLoc, 1, GL_FALSE, proj.data());

        angle::Mat4 modelview = gles1State.mModelviewMatrices.back();
        setUniformMatrix4fv(programObject, mProgramState.modelviewMatrixLoc, 1, GL_FALSE,
                            modelview.data());

        angle::Mat4 modelviewInvTr = modelview.transpose().inverse();
        setUniformMatrix4fv(programObject, mProgramState.modelviewInvTrLoc, 1, GL_FALSE,
                            modelviewInvTr.data());

        Mat4Uniform *textureMatrixBuffer = uniformBuffers.textureMatrices.data();

        for (int i = 0; i < kTexUnitCount; i++)
        {
            angle::Mat4 textureMatrix = gles1State.mTextureMatrices[i].back();
            memcpy(textureMatrixBuffer + i, textureMatrix.data(), sizeof(Mat4Uniform));
        }

        setUniformMatrix4fv(programObject, mProgramState.textureMatrixLoc, kTexUnitCount, GL_FALSE,
                            (float *)uniformBuffers.textureMatrices.data());
    }

    // Texturing
    {
        std::array<GLint, kTexUnitCount> &tex2DEnables   = uniformBuffers.tex2DEnables;
        std::array<GLint, kTexUnitCount> &texCubeEnables = uniformBuffers.texCubeEnables;

        for (int i = 0; i < kTexUnitCount; i++)
        {
            // GL_OES_cube_map allows only one of TEXTURE_2D / TEXTURE_CUBE_MAP
            // to be enabled per unit, thankfully. From the extension text:
            //
            //  --  Section 3.8.10 "Texture Application"
            //
            //      Replace the beginning sentences of the first paragraph (page 138)
            //      with:
            //
            //      "Texturing is enabled or disabled using the generic Enable
            //      and Disable commands, respectively, with the symbolic constants
            //      TEXTURE_2D or TEXTURE_CUBE_MAP_OES to enable the two-dimensional or cube
            //      map texturing respectively.  If the cube map texture and the two-
            //      dimensional texture are enabled, then cube map texturing is used.  If
            //      texturing is disabled, a rasterized fragment is passed on unaltered to the
            //      next stage of the GL (although its texture coordinates may be discarded).
            //      Otherwise, a texture value is found according to the parameter values of
            //      the currently bound texture image of the appropriate dimensionality.

            texCubeEnables[i] = gles1State.isTextureTargetEnabled(i, TextureType::CubeMap);
            tex2DEnables[i] =
                !texCubeEnables[i] && (gles1State.isTextureTargetEnabled(i, TextureType::_2D));
        }

        setUniform1iv(programObject, mProgramState.enableTexture2DLoc, kTexUnitCount,
                      tex2DEnables.data());
        setUniform1iv(programObject, mProgramState.enableTextureCubeMapLoc, kTexUnitCount,
                      texCubeEnables.data());
    }

    // Alpha test
    {
        setUniform1i(programObject, mProgramState.enableAlphaTestLoc,
                     glState->getEnableFeature(GL_ALPHA_TEST));
        setUniform1i(programObject, mProgramState.alphaFuncLoc,
                     ToGLenum(gles1State.mAlphaTestFunc));
        setUniform1f(programObject, mProgramState.alphaTestRefLoc, gles1State.mAlphaTestRef);
    }

    // Shading, materials, and lighting
    {
        setUniform1i(programObject, mProgramState.shadeModelFlatLoc,
                     gles1State.mShadeModel == ShadingModel::Flat);
        setUniform1i(programObject, mProgramState.enableLightingLoc,
                     glState->getEnableFeature(GL_LIGHTING));
        setUniform1i(programObject, mProgramState.enableRescaleNormalLoc,
                     glState->getEnableFeature(GL_RESCALE_NORMAL));
        setUniform1i(programObject, mProgramState.enableNormalizeLoc,
                     glState->getEnableFeature(GL_NORMALIZE));
        setUniform1i(programObject, mProgramState.enableColorMaterialLoc,
                     glState->getEnableFeature(GL_COLOR_MATERIAL));

        const auto &material = gles1State.mMaterial;

        setUniform4fv(programObject, mProgramState.materialAmbientLoc, 1, material.ambient.data());
        setUniform4fv(programObject, mProgramState.materialDiffuseLoc, 1, material.diffuse.data());
        setUniform4fv(programObject, mProgramState.materialSpecularLoc, 1,
                      material.specular.data());
        setUniform4fv(programObject, mProgramState.materialEmissiveLoc, 1,
                      material.emissive.data());
        setUniform1f(programObject, mProgramState.materialSpecularExponentLoc,
                     material.specularExponent);

        const auto &lightModel = gles1State.mLightModel;

        setUniform4fv(programObject, mProgramState.lightModelSceneAmbientLoc, 1,
                      lightModel.color.data());

        // TODO (lfy@google.com): Implement two-sided lighting model
        // gl->uniform1i(mProgramState.lightModelTwoSidedLoc, lightModel.twoSided);

        for (int i = 0; i < kLightCount; i++)
        {
            const auto &light              = gles1State.mLights[i];
            uniformBuffers.lightEnables[i] = light.enabled;
            memcpy(uniformBuffers.lightAmbients.data() + i, light.ambient.data(),
                   sizeof(Vec4Uniform));
            memcpy(uniformBuffers.lightDiffuses.data() + i, light.diffuse.data(),
                   sizeof(Vec4Uniform));
            memcpy(uniformBuffers.lightSpeculars.data() + i, light.specular.data(),
                   sizeof(Vec4Uniform));
            memcpy(uniformBuffers.lightPositions.data() + i, light.position.data(),
                   sizeof(Vec4Uniform));
            memcpy(uniformBuffers.lightDirections.data() + i, light.direction.data(),
                   sizeof(Vec3Uniform));
            uniformBuffers.spotlightExponents[i]    = light.spotlightExponent;
            uniformBuffers.spotlightCutoffAngles[i] = light.spotlightCutoffAngle;
            uniformBuffers.attenuationConsts[i]     = light.attenuationConst;
            uniformBuffers.attenuationLinears[i]    = light.attenuationLinear;
            uniformBuffers.attenuationQuadratics[i] = light.attenuationQuadratic;
        }

        setUniform1iv(programObject, mProgramState.lightEnablesLoc, kLightCount,
                      uniformBuffers.lightEnables.data());
        setUniform4fv(programObject, mProgramState.lightAmbientsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.lightAmbients.data());
        setUniform4fv(programObject, mProgramState.lightDiffusesLoc, kLightCount,
                      (GLfloat *)uniformBuffers.lightDiffuses.data());
        setUniform4fv(programObject, mProgramState.lightSpecularsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.lightSpeculars.data());
        setUniform4fv(programObject, mProgramState.lightPositionsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.lightPositions.data());
        setUniform3fv(programObject, mProgramState.lightDirectionsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.lightDirections.data());
        setUniform1fv(programObject, mProgramState.lightSpotlightExponentsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.spotlightExponents.data());
        setUniform1fv(programObject, mProgramState.lightSpotlightCutoffAnglesLoc, kLightCount,
                      (GLfloat *)uniformBuffers.spotlightCutoffAngles.data());
        setUniform1fv(programObject, mProgramState.lightAttenuationConstsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.attenuationConsts.data());
        setUniform1fv(programObject, mProgramState.lightAttenuationLinearsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.attenuationLinears.data());
        setUniform1fv(programObject, mProgramState.lightAttenuationQuadraticsLoc, kLightCount,
                      (GLfloat *)uniformBuffers.attenuationQuadratics.data());
    }

    // None of those are changes in sampler, so there is no need to set the GL_PROGRAM dirty.
    // Otherwise, put the dirtying here.

    return NoError();
}

int GLES1Renderer::vertexArrayIndex(ClientVertexArrayType type, const State *glState) const
{
    switch (type)
    {
        case ClientVertexArrayType::Vertex:
            return kVertexAttribIndex;
        case ClientVertexArrayType::Normal:
            return kNormalAttribIndex;
        case ClientVertexArrayType::Color:
            return kColorAttribIndex;
        case ClientVertexArrayType::PointSize:
            return kPointSizeAttribIndex;
        case ClientVertexArrayType::TextureCoord:
            return kTextureCoordAttribIndexBase + glState->gles1().getClientTextureUnit();
        default:
            UNREACHABLE();
            return 0;
    }
}

// static
int GLES1Renderer::TexCoordArrayIndex(unsigned int unit)
{
    return kTextureCoordAttribIndexBase + unit;
}

AttributesMask GLES1Renderer::getVertexArraysAttributeMask(const State *glState) const
{
    AttributesMask res;
    const GLES1State &gles1 = glState->gles1();

    ClientVertexArrayType nonTexcoordArrays[] = {
        ClientVertexArrayType::Vertex, ClientVertexArrayType::Normal, ClientVertexArrayType::Color,
        ClientVertexArrayType::PointSize,
    };

    for (const ClientVertexArrayType attrib : nonTexcoordArrays)
    {
        res.set(vertexArrayIndex(attrib, glState), gles1.isClientStateEnabled(attrib));
    }

    for (unsigned int i = 0; i < kTexUnitCount; i++)
    {
        res.set(TexCoordArrayIndex(i), gles1.isTexCoordArrayEnabled(i));
    }

    return res;
}

Shader *GLES1Renderer::getShader(GLuint handle) const
{
    return mShaderPrograms->getShader(handle);
}

Program *GLES1Renderer::getProgram(GLuint handle) const
{
    return mShaderPrograms->getProgram(handle);
}

Error GLES1Renderer::compileShader(Context *context,
                                   ShaderType shaderType,
                                   const char *src,
                                   GLuint *shaderOut)
{
    rx::ContextImpl *implementation = context->getImplementation();
    const Limitations &limitations  = implementation->getNativeLimitations();

    GLuint shader = mShaderPrograms->createShader(implementation, limitations, shaderType);

    Shader *shaderObject = getShader(shader);

    if (!shaderObject)
        return InternalError();

    shaderObject->setSource(1, &src, nullptr);
    shaderObject->compile(context);

    *shaderOut = shader;

    if (!shaderObject->isCompiled(context))
    {
        GLint infoLogLength = shaderObject->getInfoLogLength(context);
        std::vector<char> infoLog(infoLogLength, 0);
        shaderObject->getInfoLog(context, infoLogLength - 1, nullptr, infoLog.data());
        fprintf(stderr, "GLES1Renderer::%s: Info log: %s\n", __func__, infoLog.data());
        return InternalError() << "GLES1Renderer shader compile failed. Source: " << src
                               << " Info log: " << infoLog.data();
    }

    return NoError();
}

Error GLES1Renderer::linkProgram(Context *context,
                                 State *glState,
                                 GLuint vertexShader,
                                 GLuint fragmentShader,
                                 const std::unordered_map<GLint, std::string> &attribLocs,
                                 GLuint *programOut)
{
    GLuint program = mShaderPrograms->createProgram(context->getImplementation());

    Program *programObject = getProgram(program);

    if (!programObject)
    {
        return InternalError();
    }

    *programOut = program;

    programObject->attachShader(getShader(vertexShader));
    programObject->attachShader(getShader(fragmentShader));

    for (auto it : attribLocs)
    {
        GLint index             = it.first;
        const std::string &name = it.second;
        programObject->bindAttributeLocation(index, name.c_str());
    }

    ANGLE_TRY(programObject->link(context));

    glState->onProgramExecutableChange(programObject);

    if (!programObject->isLinked())
    {
        GLint infoLogLength = programObject->getInfoLogLength();
        std::vector<char> infoLog(infoLogLength, 0);
        programObject->getInfoLog(infoLogLength - 1, nullptr, infoLog.data());
        return InternalError() << "GLES1Renderer program link failed. Info log: " << infoLog.data();
    }

    programObject->detachShader(context, getShader(vertexShader));
    programObject->detachShader(context, getShader(fragmentShader));

    return NoError();
}

Error GLES1Renderer::initializeRendererProgram(Context *context, State *glState)
{
    if (mRendererProgramInitialized)
    {
        return NoError();
    }

    mShaderPrograms = new ShaderProgramManager();

    GLuint vertexShader;
    GLuint fragmentShader;

    ANGLE_TRY(compileShader(context, ShaderType::Vertex, kGLES1DrawVShader, &vertexShader));

    std::stringstream fragmentStream;
    fragmentStream << kGLES1DrawFShaderHeader;
    fragmentStream << kGLES1DrawFShaderUniformDefs;
    fragmentStream << kGLES1DrawFShaderFunctions;
    fragmentStream << kGLES1DrawFShaderMain;

    ANGLE_TRY(compileShader(context, ShaderType::Fragment, fragmentStream.str().c_str(),
                            &fragmentShader));

    std::unordered_map<GLint, std::string> attribLocs;

    attribLocs[(GLint)kVertexAttribIndex]    = "pos";
    attribLocs[(GLint)kNormalAttribIndex]    = "normal";
    attribLocs[(GLint)kColorAttribIndex]     = "color";
    attribLocs[(GLint)kPointSizeAttribIndex] = "pointsize";

    for (int i = 0; i < kTexUnitCount; i++)
    {
        std::stringstream ss;
        ss << "texcoord" << i;
        attribLocs[kTextureCoordAttribIndexBase + i] = ss.str();
    }

    ANGLE_TRY(linkProgram(context, glState, vertexShader, fragmentShader, attribLocs,
                          &mProgramState.program));

    mShaderPrograms->deleteShader(context, vertexShader);
    mShaderPrograms->deleteShader(context, fragmentShader);

    Program *programObject = getProgram(mProgramState.program);

    mProgramState.projMatrixLoc      = programObject->getUniformLocation("projection");
    mProgramState.modelviewMatrixLoc = programObject->getUniformLocation("modelview");
    mProgramState.textureMatrixLoc   = programObject->getUniformLocation("texture_matrix");
    mProgramState.modelviewInvTrLoc  = programObject->getUniformLocation("modelview_invtr");

    for (int i = 0; i < kTexUnitCount; i++)
    {
        std::stringstream ss2d;
        std::stringstream sscube;

        ss2d << "tex_sampler" << i;
        sscube << "tex_cube_sampler" << i;

        mProgramState.tex2DSamplerLocs[i] = programObject->getUniformLocation(ss2d.str().c_str());
        mProgramState.texCubeSamplerLocs[i] =
            programObject->getUniformLocation(sscube.str().c_str());
    }

    mProgramState.enableTexture2DLoc = programObject->getUniformLocation("enable_texture_2d");
    mProgramState.enableTextureCubeMapLoc =
        programObject->getUniformLocation("enable_texture_cube_map");

    mProgramState.enableAlphaTestLoc = programObject->getUniformLocation("enable_alpha_test");
    mProgramState.alphaFuncLoc       = programObject->getUniformLocation("alpha_func");
    mProgramState.alphaTestRefLoc    = programObject->getUniformLocation("alpha_test_ref");

    mProgramState.shadeModelFlatLoc = programObject->getUniformLocation("shade_model_flat");
    mProgramState.enableLightingLoc = programObject->getUniformLocation("enable_lighting");
    mProgramState.enableRescaleNormalLoc =
        programObject->getUniformLocation("enable_rescale_normal");
    mProgramState.enableNormalizeLoc = programObject->getUniformLocation("enable_normalize");
    mProgramState.enableColorMaterialLoc =
        programObject->getUniformLocation("enable_color_material");

    mProgramState.materialAmbientLoc  = programObject->getUniformLocation("material_ambient");
    mProgramState.materialDiffuseLoc  = programObject->getUniformLocation("material_diffuse");
    mProgramState.materialSpecularLoc = programObject->getUniformLocation("material_specular");
    mProgramState.materialEmissiveLoc = programObject->getUniformLocation("material_emissive");
    mProgramState.materialSpecularExponentLoc =
        programObject->getUniformLocation("material_specular_exponent");

    mProgramState.lightModelSceneAmbientLoc =
        programObject->getUniformLocation("light_model_scene_ambient");
    mProgramState.lightModelTwoSidedLoc =
        programObject->getUniformLocation("light_model_two_sided");

    mProgramState.lightEnablesLoc    = programObject->getUniformLocation("light_enables");
    mProgramState.lightAmbientsLoc   = programObject->getUniformLocation("light_ambients");
    mProgramState.lightDiffusesLoc   = programObject->getUniformLocation("light_diffuses");
    mProgramState.lightSpecularsLoc  = programObject->getUniformLocation("light_speculars");
    mProgramState.lightPositionsLoc  = programObject->getUniformLocation("light_positions");
    mProgramState.lightDirectionsLoc = programObject->getUniformLocation("light_directions");
    mProgramState.lightSpotlightExponentsLoc =
        programObject->getUniformLocation("light_spotlight_exponents");
    mProgramState.lightSpotlightCutoffAnglesLoc =
        programObject->getUniformLocation("light_spotlight_cutoff_angles");
    mProgramState.lightAttenuationConstsLoc =
        programObject->getUniformLocation("light_attenuation_consts");
    mProgramState.lightAttenuationLinearsLoc =
        programObject->getUniformLocation("light_attenuation_linears");
    mProgramState.lightAttenuationQuadraticsLoc =
        programObject->getUniformLocation("light_attenuation_quadratics");

    glState->setProgram(context, programObject);

    for (int i = 0; i < kTexUnitCount; i++)
    {
        setUniform1i(programObject, mProgramState.tex2DSamplerLocs[i], i);
        setUniform1i(programObject, mProgramState.texCubeSamplerLocs[i], i + kTexUnitCount);
    }

    glState->setObjectDirty(GL_PROGRAM);

    mRendererProgramInitialized = true;
    return NoError();
}

void GLES1Renderer::setUniform1i(Program *programObject, GLint loc, GLint value)
{
    if (loc == -1)
        return;
    programObject->setUniform1iv(loc, 1, &value);
}

void GLES1Renderer::setUniform1iv(Program *programObject,
                                  GLint loc,
                                  GLint count,
                                  const GLint *value)
{
    if (loc == -1)
        return;
    programObject->setUniform1iv(loc, count, value);
}

void GLES1Renderer::setUniformMatrix4fv(Program *programObject,
                                        GLint loc,
                                        GLint count,
                                        GLboolean transpose,
                                        const GLfloat *value)
{
    if (loc == -1)
        return;
    programObject->setUniformMatrix4fv(loc, count, transpose, value);
}

void GLES1Renderer::setUniform4fv(Program *programObject,
                                  GLint loc,
                                  GLint count,
                                  const GLfloat *value)
{
    if (loc == -1)
        return;
    programObject->setUniform4fv(loc, count, value);
}

void GLES1Renderer::setUniform3fv(Program *programObject,
                                  GLint loc,
                                  GLint count,
                                  const GLfloat *value)
{
    if (loc == -1)
        return;
    programObject->setUniform3fv(loc, count, value);
}

void GLES1Renderer::setUniform1f(Program *programObject, GLint loc, GLfloat value)
{
    if (loc == -1)
        return;
    programObject->setUniform1fv(loc, 1, &value);
}

void GLES1Renderer::setUniform1fv(Program *programObject,
                                  GLint loc,
                                  GLint count,
                                  const GLfloat *value)
{
    if (loc == -1)
        return;
    programObject->setUniform1fv(loc, count, value);
}

}  // namespace gl
