//
// Copyright 2019 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 dFdy viewport transformation.
// See header for more info.

#include "compiler/translator/tree_ops/RewriteDfdy.h"

#include "common/angleutils.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/TranslatorVulkan.h"
#include "compiler/translator/tree_util/DriverUniform.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/tree_util/SpecializationConstant.h"

namespace sh
{

namespace
{

class Traverser : public TIntermTraverser
{
  public:
    Traverser(TSymbolTable *symbolTable, SpecConst *specConst, const DriverUniform *driverUniforms);

  private:
    bool visitAggregate(Visit visit, TIntermAggregate *node) override;

    SpecConst *mSpecConst                = nullptr;
    const DriverUniform *mDriverUniforms = nullptr;
};

Traverser::Traverser(TSymbolTable *symbolTable,
                     SpecConst *specConst,
                     const DriverUniform *driverUniforms)
    : TIntermTraverser(true, false, false, symbolTable),
      mSpecConst(specConst),
      mDriverUniforms(driverUniforms)
{}

bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node)
{
    // Decide if the node represents a call to dFdx() or dFdy()
    if (node->getOp() != EOpDFdx && node->getOp() != EOpDFdy)
    {
        return true;
    }

    const bool isDFdx = node->getOp() == EOpDFdx;

    // Two transformations are done on dFdx and dFdy:
    //
    // - If pre-rotation is applied, dFdx and dFdy may need to swap their axis based on the degree
    //   of rotation.  dFdx becomes dFdy if rotation is 90 or 270 degrees.  Similarly, dFdy becomes
    //   dFdx.
    // - The result is potentially negated.  This could be due to viewport y-flip or pre-rotation.
    //
    // Accordingly, there are two variables controlling the above transformations:
    //
    // - Rotation: A vec2 that is either (0, 1) or (1, 0).  dFdx and dFdy are replaced with:
    //
    //       dFdx * Rotation.x + dFdy * Rotation.y
    //
    // - Scale: A vec2 with -1 or 1 for either x or y components.  The previous result is multiplied
    //   by this.
    //
    // Together, the above operations account for the combinations of 4 possible rotations and
    // y-flip.

    // Get the results of dFdx(operand) and dFdy(operand), and multiply them by the swizzles
    TIntermTyped *operand = node->getChildNode(0)->getAsTyped();

    TIntermTyped *dFdx = CreateBuiltInUnaryFunctionCallNode("dFdx", operand, *mSymbolTable, 300);
    TIntermTyped *dFdy =
        CreateBuiltInUnaryFunctionCallNode("dFdy", operand->deepCopy(), *mSymbolTable, 300);

    // Get rotation multiplier
    TIntermTyped *swapXY = mSpecConst->getSwapXY();
    if (swapXY == nullptr)
    {
        swapXY = mDriverUniforms->getSwapXY();
    }

    TIntermTyped *swapXMultiplier = MakeSwapXMultiplier(swapXY);
    TIntermTyped *swapYMultiplier = MakeSwapYMultiplier(swapXY->deepCopy());

    // Get flip multiplier
    TIntermTyped *flipXY = mDriverUniforms->getFlipXY(mSymbolTable, DriverUniformFlip::Fragment);

    // Multiply the flip and rotation multipliers
    TIntermTyped *xMultiplier =
        new TIntermBinary(EOpMul, isDFdx ? swapXMultiplier : swapYMultiplier,
                          (new TIntermSwizzle(flipXY->deepCopy(), {0}))->fold(nullptr));
    TIntermTyped *yMultiplier =
        new TIntermBinary(EOpMul, isDFdx ? swapYMultiplier : swapXMultiplier,
                          (new TIntermSwizzle(flipXY->deepCopy(), {1}))->fold(nullptr));

    const TOperator mulOp            = dFdx->getType().isVector() ? EOpVectorTimesScalar : EOpMul;
    TIntermTyped *rotatedFlippedDfdx = new TIntermBinary(mulOp, dFdx, xMultiplier);
    TIntermTyped *rotatedFlippedDfdy = new TIntermBinary(mulOp, dFdy, yMultiplier);

    // Sum them together into the result
    TIntermBinary *rotatedFlippedResult =
        new TIntermBinary(EOpAdd, rotatedFlippedDfdx, rotatedFlippedDfdy);

    // Replace the old dFdx() or dFdy() node with the new node that contains the corrected value
    //
    // Note the following bugs (anglebug.com/7346):
    //
    // - Side effects of operand are duplicated with the above
    // - If the direct child of this node is itself dFdx/y, its queueReplacement will not be
    //   effective as the parent is also replaced.
    queueReplacement(rotatedFlippedResult, OriginalNode::IS_DROPPED);

    return true;
}
}  // anonymous namespace

bool RewriteDfdy(TCompiler *compiler,
                 TIntermBlock *root,
                 TSymbolTable *symbolTable,
                 int shaderVersion,
                 SpecConst *specConst,
                 const DriverUniform *driverUniforms)
{
    // dFdx/dFdy is only valid in GLSL 3.0 and later.
    if (shaderVersion < 300)
    {
        return true;
    }

    Traverser traverser(symbolTable, specConst, driverUniforms);
    root->traverse(&traverser);
    return traverser.updateTree(compiler, root);
}

}  // namespace sh
