//
// Copyright (c) 2002-2014 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.
//

#include "compiler/translator/EmulatePrecision.h"

namespace
{

static void writeVectorPrecisionEmulationHelpers(
    TInfoSinkBase& sink, ShShaderOutput outputLanguage, unsigned int size)
{
    std::stringstream vecTypeStrStr;
    if (outputLanguage == SH_ESSL_OUTPUT)
        vecTypeStrStr << "highp ";
    vecTypeStrStr << "vec" << size;
    std::string vecType = vecTypeStrStr.str();

    sink <<
    vecType << " angle_frm(in " << vecType << " v) {\n"
    "    v = clamp(v, -65504.0, 65504.0);\n"
    "    " << vecType << " exponent = floor(log2(abs(v) + 1e-30)) - 10.0;\n"
    "    bvec" << size << " isNonZero = greaterThanEqual(exponent, vec" << size << "(-25.0));\n"
    "    v = v * exp2(-exponent);\n"
    "    v = sign(v) * floor(abs(v));\n"
    "    return v * exp2(exponent) * vec" << size << "(isNonZero);\n"
    "}\n";

    sink <<
    vecType << " angle_frl(in " << vecType << " v) {\n"
    "    v = clamp(v, -2.0, 2.0);\n"
    "    v = v * 256.0;\n"
    "    v = sign(v) * floor(abs(v));\n"
    "    return v * 0.00390625;\n"
    "}\n";
}

static void writeMatrixPrecisionEmulationHelper(
    TInfoSinkBase& sink, ShShaderOutput outputLanguage, unsigned int size, const char *functionName)
{
    std::stringstream matTypeStrStr;
    if (outputLanguage == SH_ESSL_OUTPUT)
        matTypeStrStr << "highp ";
    matTypeStrStr << "mat" << size;
    std::string matType = matTypeStrStr.str();

    sink << matType << " " << functionName << "(in " << matType << " m) {\n"
            "    " << matType << " rounded;\n";

    for (unsigned int i = 0; i < size; ++i)
    {
        sink << "    rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n";
    }

    sink << "    return rounded;\n"
            "}\n";
}

static void writeCommonPrecisionEmulationHelpers(TInfoSinkBase& sink, ShShaderOutput outputLanguage)
{
    // Write the angle_frm functions that round floating point numbers to
    // half precision, and angle_frl functions that round them to minimum lowp
    // precision.

    // Unoptimized version of angle_frm for single floats:
    //
    // int webgl_maxNormalExponent(in int exponentBits) {
    //     int possibleExponents = int(exp2(float(exponentBits)));
    //     int exponentBias = possibleExponents / 2 - 1;
    //     int allExponentBitsOne = possibleExponents - 1;
    //     return (allExponentBitsOne - 1) - exponentBias;
    // }
    //
    // float angle_frm(in float x) {
    //     int mantissaBits = 10;
    //     int exponentBits = 5;
    //     float possibleMantissas = exp2(float(mantissaBits));
    //     float mantissaMax = 2.0 - 1.0 / possibleMantissas;
    //     int maxNE = webgl_maxNormalExponent(exponentBits);
    //     float max = exp2(float(maxNE)) * mantissaMax;
    //     if (x > max) {
    //         return max;
    //     }
    //     if (x < -max) {
    //         return -max;
    //     }
    //     float exponent = floor(log2(abs(x)));
    //     if (abs(x) == 0.0 || exponent < -float(maxNE)) {
    //         return 0.0 * sign(x)
    //     }
    //     x = x * exp2(-(exponent - float(mantissaBits)));
    //     x = sign(x) * floor(abs(x));
    //     return x * exp2(exponent - float(mantissaBits));
    // }

    // All numbers with a magnitude less than 2^-15 are subnormal, and are
    // flushed to zero.

    // Note the constant numbers below:
    // a) 65504 is the maximum possible mantissa (1.1111111111 in binary) times
    //    2^15, the maximum normal exponent.
    // b) 10.0 is the number of mantissa bits.
    // c) -25.0 is the minimum normal half-float exponent -15.0 minus the number
    //    of mantissa bits.
    // d) + 1e-30 is to make sure the argument of log2() won't be zero. It can
    //    only affect the result of log2 on x where abs(x) < 1e-22. Since these
    //    numbers will be flushed to zero either way (2^-15 is the smallest
    //    normal positive number), this does not introduce any error.

    std::string floatType = "float";
    if (outputLanguage == SH_ESSL_OUTPUT)
        floatType = "highp float";

    sink <<
    floatType << " angle_frm(in " << floatType << " x) {\n"
    "    x = clamp(x, -65504.0, 65504.0);\n"
    "    " << floatType << " exponent = floor(log2(abs(x) + 1e-30)) - 10.0;\n"
    "    bool isNonZero = (exponent >= -25.0);\n"
    "    x = x * exp2(-exponent);\n"
    "    x = sign(x) * floor(abs(x));\n"
    "    return x * exp2(exponent) * float(isNonZero);\n"
    "}\n";

    sink <<
    floatType << " angle_frl(in " << floatType << " x) {\n"
    "    x = clamp(x, -2.0, 2.0);\n"
    "    x = x * 256.0;\n"
    "    x = sign(x) * floor(abs(x));\n"
    "    return x * 0.00390625;\n"
    "}\n";

    writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 2);
    writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 3);
    writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 4);
    for (unsigned int size = 2; size <= 4; ++size)
    {
        writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, "angle_frm");
        writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, "angle_frl");
    }
}

static void writeCompoundAssignmentPrecisionEmulation(
    TInfoSinkBase& sink, ShShaderOutput outputLanguage,
    const char *lType, const char *rType, const char *opStr, const char *opNameStr)
{
    std::string lTypeStr = lType;
    std::string rTypeStr = rType;
    if (outputLanguage == SH_ESSL_OUTPUT)
    {
        std::stringstream lTypeStrStr;
        lTypeStrStr << "highp " << lType;
        lTypeStr = lTypeStrStr.str();
        std::stringstream rTypeStrStr;
        rTypeStrStr << "highp " << rType;
        rTypeStr = rTypeStrStr.str();
    }

    // Note that y should be passed through angle_frm at the function call site,
    // but x can't be passed through angle_frm there since it is an inout parameter.
    // So only pass x and the result through angle_frm here.
    sink <<
    lTypeStr << " angle_compound_" << opNameStr << "_frm(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
    "    x = angle_frm(angle_frm(x) " << opStr << " y);\n"
    "    return x;\n"
    "}\n";
    sink <<
    lTypeStr << " angle_compound_" << opNameStr << "_frl(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
    "    x = angle_frl(angle_frm(x) " << opStr << " y);\n"
    "    return x;\n"
    "}\n";
}

const char *getFloatTypeStr(const TType& type)
{
    switch (type.getNominalSize())
    {
      case 1:
        return "float";
      case 2:
        return type.getSecondarySize() > 1 ? "mat2" : "vec2";
      case 3:
        return type.getSecondarySize() > 1 ? "mat3" : "vec3";
      case 4:
        return type.getSecondarySize() > 1 ? "mat4" : "vec4";
      default:
        UNREACHABLE();
        return NULL;
    }
}

bool canRoundFloat(const TType &type)
{
    return type.getBasicType() == EbtFloat && !type.isNonSquareMatrix() && !type.isArray() &&
        (type.getPrecision() == EbpLow || type.getPrecision() == EbpMedium);
}

TIntermAggregate *createInternalFunctionCallNode(TString name, TIntermNode *child)
{
    TIntermAggregate *callNode = new TIntermAggregate();
    callNode->setOp(EOpInternalFunctionCall);
    callNode->setName(name);
    callNode->getSequence()->push_back(child);
    return callNode;
}

TIntermAggregate *createRoundingFunctionCallNode(TIntermTyped *roundedChild)
{
    TString roundFunctionName;
    if (roundedChild->getPrecision() == EbpMedium)
        roundFunctionName = "angle_frm";
    else
        roundFunctionName = "angle_frl";
    return createInternalFunctionCallNode(roundFunctionName, roundedChild);
}

TIntermAggregate *createCompoundAssignmentFunctionCallNode(TIntermTyped *left, TIntermTyped *right, const char *opNameStr)
{
    std::stringstream strstr;
    if (left->getPrecision() == EbpMedium)
        strstr << "angle_compound_" << opNameStr << "_frm";
    else
        strstr << "angle_compound_" << opNameStr << "_frl";
    TString functionName = strstr.str().c_str();
    TIntermAggregate *callNode = createInternalFunctionCallNode(functionName, left);
    callNode->getSequence()->push_back(right);
    return callNode;
}

bool parentUsesResult(TIntermNode* parent, TIntermNode* node)
{
    if (!parent)
    {
        return false;
    }

    TIntermAggregate *aggParent = parent->getAsAggregate();
    // If the parent's op is EOpSequence, the result is not assigned anywhere,
    // so rounding it is not needed. In particular, this can avoid a lot of
    // unnecessary rounding of unused return values of assignment.
    if (aggParent && aggParent->getOp() == EOpSequence)
    {
        return false;
    }
    if (aggParent && aggParent->getOp() == EOpComma && (aggParent->getSequence()->back() != node))
    {
        return false;
    }
    return true;
}

}  // namespace anonymous

EmulatePrecision::EmulatePrecision()
    : TIntermTraverser(true, true, true),
      mDeclaringVariables(false),
      mInLValue(false),
      mInFunctionCallOutParameter(false)
{}

void EmulatePrecision::visitSymbol(TIntermSymbol *node)
{
    if (canRoundFloat(node->getType()) &&
        !mDeclaringVariables && !mInLValue && !mInFunctionCallOutParameter)
    {
        TIntermNode *parent = getParentNode();
        TIntermNode *replacement = createRoundingFunctionCallNode(node);
        mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
    }
}


bool EmulatePrecision::visitBinary(Visit visit, TIntermBinary *node)
{
    bool visitChildren = true;

    if (node->isAssignment())
    {
        if (visit == PreVisit)
            mInLValue = true;
        else if (visit == InVisit)
            mInLValue = false;
    }

    TOperator op = node->getOp();

    // RHS of initialize is not being declared.
    if (op == EOpInitialize && visit == InVisit)
        mDeclaringVariables = false;

    if ((op == EOpIndexDirectStruct || op == EOpVectorSwizzle) && visit == InVisit)
        visitChildren = false;

    if (visit != PreVisit)
        return visitChildren;

    const TType& type = node->getType();
    bool roundFloat = canRoundFloat(type);

    if (roundFloat) {
        switch (op) {
          // Math operators that can result in a float may need to apply rounding to the return
          // value. Note that in the case of assignment, the rounding is applied to its return
          // value here, not the value being assigned.
          case EOpAssign:
          case EOpAdd:
          case EOpSub:
          case EOpMul:
          case EOpDiv:
          case EOpVectorTimesScalar:
          case EOpVectorTimesMatrix:
          case EOpMatrixTimesVector:
          case EOpMatrixTimesScalar:
          case EOpMatrixTimesMatrix:
          {
            TIntermNode *parent = getParentNode();
            if (!parentUsesResult(parent, node))
            {
                break;
            }
            TIntermNode *replacement = createRoundingFunctionCallNode(node);
            mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
            break;
          }

          // Compound assignment cases need to replace the operator with a function call.
          case EOpAddAssign:
          {
            mEmulateCompoundAdd.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
            TIntermNode *parent = getParentNode();
            TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "add");
            mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
            break;
          }
          case EOpSubAssign:
          {
            mEmulateCompoundSub.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
            TIntermNode *parent = getParentNode();
            TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "sub");
            mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
            break;
          }
          case EOpMulAssign:
          case EOpVectorTimesMatrixAssign:
          case EOpVectorTimesScalarAssign:
          case EOpMatrixTimesScalarAssign:
          case EOpMatrixTimesMatrixAssign:
          {
            mEmulateCompoundMul.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
            TIntermNode *parent = getParentNode();
            TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "mul");
            mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
            break;
          }
          case EOpDivAssign:
          {
            mEmulateCompoundDiv.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
            TIntermNode *parent = getParentNode();
            TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "div");
            mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
            break;
          }
          default:
            // The rest of the binary operations should not need precision emulation.
            break;
        }
    }
    return visitChildren;
}

bool EmulatePrecision::visitAggregate(Visit visit, TIntermAggregate *node)
{
    bool visitChildren = true;
    switch (node->getOp())
    {
      case EOpSequence:
      case EOpConstructStruct:
        // No special handling
        break;
      case EOpFunction:
        if (visit == PreVisit)
        {
            const TIntermSequence &sequence = *(node->getSequence());
            TIntermSequence::const_iterator seqIter = sequence.begin();
            TIntermAggregate *params = (*seqIter)->getAsAggregate();
            ASSERT(params != NULL);
            ASSERT(params->getOp() == EOpParameters);
            mFunctionMap[node->getName()] = params->getSequence();
        }
        break;
      case EOpPrototype:
        if (visit == PreVisit)
            mFunctionMap[node->getName()] = node->getSequence();
        visitChildren = false;
        break;
      case EOpParameters:
        visitChildren = false;
        break;
      case EOpInvariantDeclaration:
        visitChildren = false;
        break;
      case EOpDeclaration:
        // Variable declaration.
        if (visit == PreVisit)
        {
            mDeclaringVariables = true;
        }
        else if (visit == InVisit)
        {
            mDeclaringVariables = true;
        }
        else
        {
            mDeclaringVariables = false;
        }
        break;
      case EOpFunctionCall:
      {
        // Function call.
        bool inFunctionMap = (mFunctionMap.find(node->getName()) != mFunctionMap.end());
        if (visit == PreVisit)
        {
            // User-defined function return values are not rounded, this relies on that
            // calculations producing the value were rounded.
            TIntermNode *parent = getParentNode();
            if (canRoundFloat(node->getType()) && !inFunctionMap && parentUsesResult(parent, node))
            {
                TIntermNode *replacement = createRoundingFunctionCallNode(node);
                mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
            }

            if (inFunctionMap)
            {
                mSeqIterStack.push_back(mFunctionMap[node->getName()]->begin());
                if (mSeqIterStack.back() != mFunctionMap[node->getName()]->end())
                {
                    TQualifier qualifier = (*mSeqIterStack.back())->getAsTyped()->getQualifier();
                    mInFunctionCallOutParameter = (qualifier == EvqOut || qualifier == EvqInOut);
                }
            }
            else
            {
                // The function is not user-defined - it is likely built-in texture function.
                // Assume that those do not have out parameters.
                mInFunctionCallOutParameter = false;
            }
        }
        else if (visit == InVisit)
        {
            if (inFunctionMap)
            {
                ++mSeqIterStack.back();
                TQualifier qualifier = (*mSeqIterStack.back())->getAsTyped()->getQualifier();
                mInFunctionCallOutParameter = (qualifier == EvqOut || qualifier == EvqInOut);
            }
        }
        else
        {
            if (inFunctionMap)
            {
                mSeqIterStack.pop_back();
                mInFunctionCallOutParameter = false;
            }
        }
        break;
      }
      default:
        TIntermNode *parent = getParentNode();
        if (canRoundFloat(node->getType()) && visit == PreVisit && parentUsesResult(parent, node))
        {
            TIntermNode *replacement = createRoundingFunctionCallNode(node);
            mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
        }
        break;
    }
    return visitChildren;
}

bool EmulatePrecision::visitUnary(Visit visit, TIntermUnary *node)
{
    switch (node->getOp())
    {
      case EOpNegative:
      case EOpVectorLogicalNot:
      case EOpLogicalNot:
        break;
      case EOpPostIncrement:
      case EOpPostDecrement:
      case EOpPreIncrement:
      case EOpPreDecrement:
        if (visit == PreVisit)
            mInLValue = true;
        else if (visit == PostVisit)
            mInLValue = false;
        break;
      default:
        if (canRoundFloat(node->getType()) && visit == PreVisit)
        {
            TIntermNode *parent = getParentNode();
            TIntermNode *replacement = createRoundingFunctionCallNode(node);
            mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
        }
        break;
    }

    return true;
}

void EmulatePrecision::writeEmulationHelpers(TInfoSinkBase& sink, ShShaderOutput outputLanguage)
{
    // Other languages not yet supported
    ASSERT(outputLanguage == SH_GLSL_OUTPUT || outputLanguage == SH_ESSL_OUTPUT);
    writeCommonPrecisionEmulationHelpers(sink, outputLanguage);

    EmulationSet::const_iterator it;
    for (it = mEmulateCompoundAdd.begin(); it != mEmulateCompoundAdd.end(); it++)
        writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "+", "add");
    for (it = mEmulateCompoundSub.begin(); it != mEmulateCompoundSub.end(); it++)
        writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "-", "sub");
    for (it = mEmulateCompoundDiv.begin(); it != mEmulateCompoundDiv.end(); it++)
        writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "/", "div");
    for (it = mEmulateCompoundMul.begin(); it != mEmulateCompoundMul.end(); it++)
        writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "*", "mul");
}

