//
// Copyright (c) 2012 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/InfoSink.h"
#include "compiler/translator/ParseContext.h"
#include "compiler/translator/depgraph/DependencyGraphOutput.h"
#include "compiler/translator/timing/RestrictFragmentShaderTiming.h"

RestrictFragmentShaderTiming::RestrictFragmentShaderTiming(TInfoSinkBase& sink)
    : mSink(sink)
    , mNumErrors(0)
{
    // Sampling ops found only in fragment shaders.
    mSamplingOps.insert("texture2D(s21;vf2;f1;");
    mSamplingOps.insert("texture2DProj(s21;vf3;f1;");
    mSamplingOps.insert("texture2DProj(s21;vf4;f1;");
    mSamplingOps.insert("textureCube(sC1;vf3;f1;");
    // Sampling ops found in both vertex and fragment shaders.
    mSamplingOps.insert("texture2D(s21;vf2;");
    mSamplingOps.insert("texture2DProj(s21;vf3;");
    mSamplingOps.insert("texture2DProj(s21;vf4;");
    mSamplingOps.insert("textureCube(sC1;vf3;");
    // Sampling ops provided by OES_EGL_image_external.
    mSamplingOps.insert("texture2D(1;vf2;");
    mSamplingOps.insert("texture2DProj(1;vf3;");
    mSamplingOps.insert("texture2DProj(1;vf4;");
    // Sampling ops provided by ARB_texture_rectangle.
    mSamplingOps.insert("texture2DRect(1;vf2;");
    mSamplingOps.insert("texture2DRectProj(1;vf3;");
    mSamplingOps.insert("texture2DRectProj(1;vf4;");
    // Sampling ops provided by EXT_shader_texture_lod.
    mSamplingOps.insert("texture2DLodEXT(1;vf2;f1;");
    mSamplingOps.insert("texture2DProjLodEXT(1;vf3;f1;");
    mSamplingOps.insert("texture2DProjLodEXT(1;vf4;f1;");
    mSamplingOps.insert("textureCubeLodEXT(1;vf4;f1;");
    mSamplingOps.insert("texture2DGradEXT(1;vf2;vf2;vf2;");
    mSamplingOps.insert("texture2DProjGradEXT(1;vf3;vf2;vf2;");
    mSamplingOps.insert("texture2DProjGradEXT(1;vf4;vf2;vf2;");
    mSamplingOps.insert("textureCubeGradEXT(1;vf3;vf3;vf3;");
}

// FIXME(mvujovic): We do not know if the execution time of built-in operations like sin, pow, etc.
// can vary based on the value of the input arguments. If so, we should restrict those as well.
void RestrictFragmentShaderTiming::enforceRestrictions(const TDependencyGraph& graph)
{
    mNumErrors = 0;

    // FIXME(mvujovic): The dependency graph does not support user defined function calls right now,
    // so we generate errors for them.
    validateUserDefinedFunctionCallUsage(graph);

    // Starting from each sampler, traverse the dependency graph and generate an error each time we
    // hit a node where sampler dependent values are not allowed.
    for (TGraphSymbolVector::const_iterator iter = graph.beginSamplerSymbols();
         iter != graph.endSamplerSymbols();
         ++iter)
    {
        TGraphSymbol* samplerSymbol = *iter;
        clearVisited();
        samplerSymbol->traverse(this);
    }
}

void RestrictFragmentShaderTiming::validateUserDefinedFunctionCallUsage(const TDependencyGraph& graph)
{
    for (TFunctionCallVector::const_iterator iter = graph.beginUserDefinedFunctionCalls();
         iter != graph.endUserDefinedFunctionCalls();
         ++iter)
    {
        TGraphFunctionCall* functionCall = *iter;
        beginError(functionCall->getIntermFunctionCall());
        mSink << "A call to a user defined function is not permitted.\n";
    }
}

void RestrictFragmentShaderTiming::beginError(const TIntermNode* node)
{
    ++mNumErrors;
    mSink.prefix(EPrefixError);
    mSink.location(node->getLine());
}

bool RestrictFragmentShaderTiming::isSamplingOp(const TIntermAggregate* intermFunctionCall) const
{
    return !intermFunctionCall->isUserDefined() &&
           mSamplingOps.find(intermFunctionCall->getName()) != mSamplingOps.end();
}

void RestrictFragmentShaderTiming::visitArgument(TGraphArgument* parameter)
{
    // Texture cache access time might leak sensitive information.
    // Thus, we restrict sampler dependent values from affecting the coordinate or LOD bias of a
    // sampling operation.
    if (isSamplingOp(parameter->getIntermFunctionCall())) {
        switch (parameter->getArgumentNumber()) {
            case 1:
                // Second argument (coord)
                beginError(parameter->getIntermFunctionCall());
                mSink << "An expression dependent on a sampler is not permitted to be the"
                      << " coordinate argument of a sampling operation.\n";
                break;
            case 2:
                // Third argument (bias)
                beginError(parameter->getIntermFunctionCall());
                mSink << "An expression dependent on a sampler is not permitted to be the"
                      << " bias argument of a sampling operation.\n";
                break;
            default:
                // First argument (sampler)
                break;
        }
    }
}

void RestrictFragmentShaderTiming::visitSelection(TGraphSelection* selection)
{
    beginError(selection->getIntermSelection());
    mSink << "An expression dependent on a sampler is not permitted in a conditional statement.\n";
}

void RestrictFragmentShaderTiming::visitLoop(TGraphLoop* loop)
{
    beginError(loop->getIntermLoop());
    mSink << "An expression dependent on a sampler is not permitted in a loop condition.\n";
}

void RestrictFragmentShaderTiming::visitLogicalOp(TGraphLogicalOp* logicalOp)
{
    beginError(logicalOp->getIntermLogicalOp());
    mSink << "An expression dependent on a sampler is not permitted on the left hand side of a logical "
          << logicalOp->getOpString()
          << " operator.\n";
}
