//
// Copyright (c) 2016 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.
//
// Implementation of texelFetchOffset translation issue workaround.
// See header for more info.

#include "compiler/translator/RewriteTexelFetchOffset.h"

#include "common/angleutils.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/SymbolTable.h"

namespace sh
{

namespace
{

class Traverser : public TIntermTraverser
{
  public:
    static void Apply(TIntermNode *root,
                      const TSymbolTable &symbolTable,
                      int shaderVersion);

  private:
    Traverser(const TSymbolTable &symbolTable, int shaderVersion);
    bool visitAggregate(Visit visit, TIntermAggregate *node) override;
    void nextIteration();

    const TSymbolTable *symbolTable;
    const int shaderVersion;
    bool mFound = false;
};

Traverser::Traverser(const TSymbolTable &symbolTable, int shaderVersion)
    : TIntermTraverser(true, false, false), symbolTable(&symbolTable), shaderVersion(shaderVersion)
{
}

// static
void Traverser::Apply(TIntermNode *root,
                      const TSymbolTable &symbolTable,
                      int shaderVersion)
{
    Traverser traverser(symbolTable, shaderVersion);
    do
    {
        traverser.nextIteration();
        root->traverse(&traverser);
        if (traverser.mFound)
        {
            traverser.updateTree();
        }
    } while (traverser.mFound);
}

void Traverser::nextIteration()
{
    mFound = false;
}

bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node)
{
    if (mFound)
    {
        return false;
    }

    // Decide if the node represents the call of texelFetchOffset.
    if (node->getOp() != EOpFunctionCall || node->isUserDefined())
    {
        return true;
    }

    if (node->getFunctionSymbolInfo()->getName().compare(0, 16, "texelFetchOffset") != 0)
    {
        return true;
    }

    // Potential problem case detected, apply workaround.
    const TIntermSequence *sequence = node->getSequence();
    ASSERT(sequence->size() == 4u);

    // Decide if there is a 2DArray sampler.
    bool is2DArray = node->getFunctionSymbolInfo()->getName().find("s2a1") != TString::npos;

    // Create new argument list from node->getName().
    // e.g. Get "(is2a1;vi3;i1;" from "texelFetchOffset(is2a1;vi3;i1;vi2;"
    TString newArgs = node->getFunctionSymbolInfo()->getName().substr(
        16, node->getFunctionSymbolInfo()->getName().length() - 20);
    TString newName           = "texelFetch" + newArgs;
    TSymbol *texelFetchSymbol = symbolTable->findBuiltIn(newName, shaderVersion);
    ASSERT(texelFetchSymbol);
    int uniqueId = texelFetchSymbol->getUniqueId();

    // Create new node that represents the call of function texelFetch.
    // Its argument list will be: texelFetch(sampler, Position+offset, lod).
    TIntermAggregate *texelFetchNode = new TIntermAggregate(EOpFunctionCall);
    texelFetchNode->getFunctionSymbolInfo()->setName(newName);
    texelFetchNode->getFunctionSymbolInfo()->setId(uniqueId);
    texelFetchNode->setType(node->getType());
    texelFetchNode->setLine(node->getLine());

    // sampler
    texelFetchNode->getSequence()->push_back(sequence->at(0));

    // Position
    TIntermTyped *texCoordNode = sequence->at(1)->getAsTyped();
    ASSERT(texCoordNode);

    // offset
    TIntermTyped *offsetNode = nullptr;
    ASSERT(sequence->at(3)->getAsTyped());
    if (is2DArray)
    {
        // For 2DArray samplers, Position is ivec3 and offset is ivec2;
        // So offset must be converted into an ivec3 before being added to Position.
        TIntermAggregate *constructIVec3Node = new TIntermAggregate(EOpConstructIVec3);
        constructIVec3Node->setLine(texCoordNode->getLine());
        constructIVec3Node->setType(texCoordNode->getType());

        constructIVec3Node->getSequence()->push_back(sequence->at(3)->getAsTyped());

        TConstantUnion *zero = new TConstantUnion();
        zero->setIConst(0);
        TType *intType = new TType(EbtInt);

        TIntermConstantUnion *zeroNode = new TIntermConstantUnion(zero, *intType);
        constructIVec3Node->getSequence()->push_back(zeroNode);

        offsetNode = constructIVec3Node;
    }
    else
    {
        offsetNode = sequence->at(3)->getAsTyped();
    }

    // Position+offset
    TIntermBinary *add = new TIntermBinary(EOpAdd, texCoordNode, offsetNode);
    add->setLine(texCoordNode->getLine());
    texelFetchNode->getSequence()->push_back(add);

    // lod
    texelFetchNode->getSequence()->push_back(sequence->at(2));

    ASSERT(texelFetchNode->getSequence()->size() == 3u);

    // Replace the old node by this new node.
    queueReplacement(node, texelFetchNode, OriginalNode::IS_DROPPED);
    mFound = true;
    return false;
}

}  // anonymous namespace

void RewriteTexelFetchOffset(TIntermNode *root,
                             const TSymbolTable &symbolTable,
                             int shaderVersion)
{
    // texelFetchOffset is only valid in GLSL 3.0 and later.
    if (shaderVersion < 300)
        return;

    Traverser::Apply(root, symbolTable, shaderVersion);
}

}  // namespace sh