// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/shader_translator.h"

#include <stddef.h>
#include <string.h>
#include <algorithm>

#include "base/at_exit.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/observer_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_version_info.h"

namespace gpu {
namespace gles2 {

namespace {

class ShaderTranslatorInitializer {
 public:
  ShaderTranslatorInitializer() {
    TRACE_EVENT0("gpu", "ShInitialize");
    CHECK(sh::Initialize());
  }

  ~ShaderTranslatorInitializer() {
    TRACE_EVENT0("gpu", "ShFinalize");
    sh::Finalize();
  }
};

base::LazyInstance<ShaderTranslatorInitializer>::DestructorAtExit
    g_translator_initializer = LAZY_INSTANCE_INITIALIZER;

void GetAttributes(ShHandle compiler, AttributeMap* var_map) {
  if (!var_map)
    return;
  var_map->clear();
  const std::vector<sh::Attribute>* attribs = sh::GetAttributes(compiler);
  if (attribs) {
    for (size_t ii = 0; ii < attribs->size(); ++ii)
      (*var_map)[(*attribs)[ii].mappedName] = (*attribs)[ii];
  }
}

void GetUniforms(ShHandle compiler, UniformMap* var_map) {
  if (!var_map)
    return;
  var_map->clear();
  const std::vector<sh::Uniform>* uniforms = sh::GetUniforms(compiler);
  if (uniforms) {
    for (size_t ii = 0; ii < uniforms->size(); ++ii)
      (*var_map)[(*uniforms)[ii].mappedName] = (*uniforms)[ii];
  }
}

void GetVaryings(ShHandle compiler, VaryingMap* var_map) {
  if (!var_map)
    return;
  var_map->clear();
  const std::vector<sh::Varying>* varyings = sh::GetVaryings(compiler);
  if (varyings) {
    for (size_t ii = 0; ii < varyings->size(); ++ii)
      (*var_map)[(*varyings)[ii].mappedName] = (*varyings)[ii];
  }
}
void GetOutputVariables(ShHandle compiler, OutputVariableList* var_list) {
  if (!var_list)
    return;
  *var_list = *sh::GetOutputVariables(compiler);
}

void GetInterfaceBlocks(ShHandle compiler, InterfaceBlockMap* var_map) {
  if (!var_map)
    return;
  var_map->clear();
  const std::vector<sh::InterfaceBlock>* interface_blocks =
      sh::GetInterfaceBlocks(compiler);
  if (interface_blocks) {
    for (const auto& block : *interface_blocks) {
      (*var_map)[block.mappedName] = block;
    }
  }
}

}  // namespace

ShShaderOutput ShaderTranslator::GetShaderOutputLanguageForContext(
    const gl::GLVersionInfo& version_info) {
  if (version_info.is_es) {
    return SH_ESSL_OUTPUT;
  }

  // Determine the GLSL version based on OpenGL specification.

  unsigned context_version =
      version_info.major_version * 100 + version_info.minor_version * 10;
  if (context_version >= 450) {
    // OpenGL specs from 4.2 on specify that the core profile is "also
    // guaranteed to support all previous versions of the OpenGL Shading
    // Language back to version 1.40". For simplicity, we assume future
    // specs do not unspecify this. If they did, they could unspecify
    // glGetStringi(GL_SHADING_LANGUAGE_VERSION, k), too.
    // Since current context >= 4.5, use GLSL 4.50 core.
    return SH_GLSL_450_CORE_OUTPUT;
  } else if (context_version == 440) {
    return SH_GLSL_440_CORE_OUTPUT;
  } else if (context_version == 430) {
    return SH_GLSL_430_CORE_OUTPUT;
  } else if (context_version == 420) {
    return SH_GLSL_420_CORE_OUTPUT;
  } else if (context_version == 410) {
    return SH_GLSL_410_CORE_OUTPUT;
  } else if (context_version == 400) {
    return SH_GLSL_400_CORE_OUTPUT;
  } else if (context_version == 330) {
    return SH_GLSL_330_CORE_OUTPUT;
  } else if (context_version == 320) {
    return SH_GLSL_150_CORE_OUTPUT;
  }

  // Before OpenGL 3.2 we use the compatibility profile. Shading
  // language version 130 restricted how sampler arrays can be indexed
  // in loops, which causes problems like crbug.com/550487 .
  //
  // Also for any future specs that might be introduced between OpenGL
  // 3.3 and OpenGL 4.0, at the time of writing, we use the
  // compatibility profile.
  return SH_GLSL_COMPATIBILITY_OUTPUT;
}

ShaderTranslator::DestructionObserver::DestructionObserver() = default;

ShaderTranslator::DestructionObserver::~DestructionObserver() = default;

ShaderTranslator::ShaderTranslator() : compiler_(nullptr), compile_options_{} {}

bool ShaderTranslator::Init(GLenum shader_type,
                            ShShaderSpec shader_spec,
                            const ShBuiltInResources* resources,
                            ShShaderOutput shader_output_language,
                            const ShCompileOptions& driver_bug_workarounds,
                            bool gl_shader_interm_output) {
  // Make sure Init is called only once.
  DCHECK(compiler_ == nullptr);
  DCHECK(shader_type == GL_FRAGMENT_SHADER || shader_type == GL_VERTEX_SHADER);
  DCHECK(shader_spec == SH_GLES2_SPEC || shader_spec == SH_WEBGL_SPEC ||
         shader_spec == SH_GLES3_SPEC || shader_spec == SH_WEBGL2_SPEC);
  DCHECK(resources != nullptr);

  g_translator_initializer.Get();


  {
    TRACE_EVENT0("gpu", "ShConstructCompiler");
    compiler_ = sh::ConstructCompiler(shader_type, shader_spec,
                                      shader_output_language, resources);
  }

  compile_options_ = driver_bug_workarounds;
  compile_options_.objectCode = true;
  compile_options_.variables = true;
  compile_options_.enforcePackingRestrictions = true;
  compile_options_.limitExpressionComplexity = true;
  compile_options_.limitCallStackDepth = true;
  compile_options_.clampIndirectArrayBounds = true;
  compile_options_.emulateGLDrawID = true;
  compile_options_.emulateGLBaseVertexBaseInstance = true;

  std::string compile_options_string =
      "objectCode:variables:enforcePackingRestrictions:"
      "limitExpressionComplexity:limitCallStackDepth:clampIndirectArrayBounds:"
      "emulateGLDrawID:emulateGLBaseVertexBaseInstance";

  if (gl_shader_interm_output) {
    compile_options_.intermediateTree = true;
    compile_options_string += ":intermediateTree";
  }

  switch (shader_spec) {
    case SH_WEBGL_SPEC:
    case SH_WEBGL2_SPEC:
      compile_options_.initOutputVariables = true;
      break;
    default:
      break;
  }

  // Build the options string for additional features that may be set by the
  // caller.  Note that this code is used by the validating command decoder,
  // which is deprecated.  No new features are expected to be enabled, neither
  // is it expected for there to be new users of this code.
  if (compile_options_.initOutputVariables)
    compile_options_string += ":initOutputVariables";
  if (compile_options_.initGLPosition)
    compile_options_string += ":initGLPosition";
  if (compile_options_.unfoldShortCircuit)
    compile_options_string += ":unfoldShortCircuit";
  if (compile_options_.scalarizeVecAndMatConstructorArgs)
    compile_options_string += ":scalarizeVecAndMatConstructorArgs";
  if (compile_options_.regenerateStructNames)
    compile_options_string += ":regenerateStructNames";
  if (compile_options_.emulateAbsIntFunction)
    compile_options_string += ":emulateAbsIntFunction";
  if (compile_options_.rewriteTexelFetchOffsetToTexelFetch)
    compile_options_string += ":rewriteTexelFetchOffsetToTexelFetch";
  if (compile_options_.addAndTrueToLoopCondition)
    compile_options_string += ":addAndTrueToLoopCondition";
  if (compile_options_.rewriteDoWhileLoops)
    compile_options_string += ":rewriteDoWhileLoops";
  if (compile_options_.emulateIsnanFloatFunction)
    compile_options_string += ":emulateIsnanFloatFunction";
  if (compile_options_.useUnusedStandardSharedBlocks)
    compile_options_string += ":useUnusedStandardSharedBlocks";
  if (compile_options_.removeInvariantAndCentroidForESSL3)
    compile_options_string += ":removeInvariantAndCentroidForESSL3";
  if (compile_options_.rewriteFloatUnaryMinusOperator)
    compile_options_string += ":rewriteFloatUnaryMinusOperator";
  if (compile_options_.dontUseLoopsToInitializeVariables)
    compile_options_string += ":dontUseLoopsToInitializeVariables";
  if (compile_options_.removeDynamicIndexingOfSwizzledVector)
    compile_options_string += ":removeDynamicIndexingOfSwizzledVector";
  if (compile_options_.initializeUninitializedLocals)
    compile_options_string += ":initializeUninitializedLocals";

  if (compiler_) {
    options_affecting_compilation_ =
        base::MakeRefCounted<OptionsAffectingCompilationString>(
            ":CompileOptions:" + compile_options_string +
            sh::GetBuiltInResourcesString(compiler_));
  }

  return compiler_ != nullptr;
}

const ShCompileOptions& ShaderTranslator::GetCompileOptions() const {
  return compile_options_;
}

bool ShaderTranslator::Translate(
    const std::string& shader_source,
    std::string* info_log,
    std::string* translated_source,
    int* shader_version,
    AttributeMap* attrib_map,
    UniformMap* uniform_map,
    VaryingMap* varying_map,
    InterfaceBlockMap* interface_block_map,
    OutputVariableList* output_variable_list) const {
  // Make sure this instance is initialized.
  DCHECK(compiler_ != nullptr);

  bool success = false;
  {
    TRACE_EVENT0("gpu", "ShCompile");
    const char* const shader_strings[] = { shader_source.c_str() };
    success = sh::Compile(compiler_, shader_strings, 1, GetCompileOptions());
  }
  if (success) {
    // Get translated shader.
    if (translated_source) {
      *translated_source = sh::GetObjectCode(compiler_);
    }
    // Get shader version.
    *shader_version = sh::GetShaderVersion(compiler_);
    // Get info for attribs, uniforms, varyings and output variables.
    GetAttributes(compiler_, attrib_map);
    GetUniforms(compiler_, uniform_map);
    GetVaryings(compiler_, varying_map);
    GetInterfaceBlocks(compiler_, interface_block_map);
    GetOutputVariables(compiler_, output_variable_list);
  }

  // Get info log.
  if (info_log) {
    *info_log = sh::GetInfoLog(compiler_);
  }

  // We don't need results in the compiler anymore.
  sh::ClearResults(compiler_);

  return success;
}

OptionsAffectingCompilationString*
ShaderTranslator::GetStringForOptionsThatWouldAffectCompilation() const {
  return options_affecting_compilation_.get();
}

void ShaderTranslator::AddDestructionObserver(
    DestructionObserver* observer) {
  destruction_observers_.AddObserver(observer);
}

void ShaderTranslator::RemoveDestructionObserver(
    DestructionObserver* observer) {
  destruction_observers_.RemoveObserver(observer);
}

ShaderTranslator::~ShaderTranslator() {
  for (auto& observer : destruction_observers_)
    observer.OnDestruct(this);

  if (compiler_ != nullptr)
    sh::Destruct(compiler_);
}

}  // namespace gles2
}  // namespace gpu