// Copyright (c) 2012 The Chromium 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 "gpu/command_buffer/service/shader_translator.h"

#include <string.h>
#include <GLES2/gl2.h>
#include <algorithm>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
#include "gpu/command_buffer/service/gpu_switches.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(ShInitialize());
  }

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

base::LazyInstance<ShaderTranslatorInitializer> 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 = ShGetAttributes(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 = ShGetUniforms(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 = ShGetVaryings(compiler);
  if (varyings) {
    for (size_t ii = 0; ii < varyings->size(); ++ii)
      (*var_map)[(*varyings)[ii].mappedName] = (*varyings)[ii];
  }
}

void GetNameHashingInfo(ShHandle compiler, NameMap* name_map) {
  if (!name_map)
    return;
  name_map->clear();

  typedef std::map<std::string, std::string> NameMapANGLE;
  const NameMapANGLE* angle_map = ShGetNameHashingMap(compiler);
  DCHECK(angle_map);

  for (NameMapANGLE::const_iterator iter = angle_map->begin();
       iter != angle_map->end(); ++iter) {
    // Note that in ANGLE, the map is (original_name, hash);
    // here, we want (hash, original_name).
    (*name_map)[iter->second] = iter->first;
  }
}

}  // namespace

ShShaderOutput ShaderTranslator::GetShaderOutputLanguageForContext(
    const gfx::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;
  } else if (context_version == 310) {
    return SH_GLSL_140_OUTPUT;
  } else if (context_version == 300) {
    return SH_GLSL_130_OUTPUT;
  }

  // Before OpenGL 3.0 we use compatibility profile. Also for future
  // specs between OpenGL 3.3 and OpenGL 4.0, at the time of writing,
  // we use compatibility profile.
  return SH_GLSL_COMPATIBILITY_OUTPUT;
}

ShaderTranslator::DestructionObserver::DestructionObserver() {
}

ShaderTranslator::DestructionObserver::~DestructionObserver() {
}

ShaderTranslator::ShaderTranslator()
    : compiler_(NULL),
      driver_bug_workarounds_(static_cast<ShCompileOptions>(0)) {
}

bool ShaderTranslator::Init(GLenum shader_type,
                            ShShaderSpec shader_spec,
                            const ShBuiltInResources* resources,
                            ShShaderOutput shader_output_language,
                            ShCompileOptions driver_bug_workarounds) {
  // Make sure Init is called only once.
  DCHECK(compiler_ == NULL);
  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 != NULL);

  g_translator_initializer.Get();


  {
    TRACE_EVENT0("gpu", "ShConstructCompiler");
    compiler_ = ShConstructCompiler(shader_type, shader_spec,
                                    shader_output_language, resources);
  }
  driver_bug_workarounds_ = driver_bug_workarounds;
  return compiler_ != NULL;
}

int ShaderTranslator::GetCompileOptions() const {
  int compile_options =
      SH_OBJECT_CODE | SH_VARIABLES | SH_ENFORCE_PACKING_RESTRICTIONS |
      SH_LIMIT_EXPRESSION_COMPLEXITY | SH_LIMIT_CALL_STACK_DEPTH |
      SH_CLAMP_INDIRECT_ARRAY_BOUNDS;

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kGLShaderIntermOutput))
    compile_options |= SH_INTERMEDIATE_TREE;

  compile_options |= driver_bug_workarounds_;

  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,
                                 NameMap* name_map) const {
  // Make sure this instance is initialized.
  DCHECK(compiler_ != NULL);

  bool success = false;
  {
    TRACE_EVENT0("gpu", "ShCompile");
    const char* const shader_strings[] = { shader_source.c_str() };
    success = ShCompile(
        compiler_, shader_strings, 1, GetCompileOptions());
  }
  if (success) {
    // Get translated shader.
    if (translated_source) {
      *translated_source = ShGetObjectCode(compiler_);
    }
    // Get shader version.
    *shader_version = ShGetShaderVersion(compiler_);
    // Get info for attribs, uniforms, and varyings.
    GetAttributes(compiler_, attrib_map);
    GetUniforms(compiler_, uniform_map);
    GetVaryings(compiler_, varying_map);
    // Get info for name hashing.
    GetNameHashingInfo(compiler_, name_map);
  }

  // Get info log.
  if (info_log) {
    *info_log = ShGetInfoLog(compiler_);
  }

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

  return success;
}

std::string ShaderTranslator::GetStringForOptionsThatWouldAffectCompilation()
    const {
  DCHECK(compiler_ != NULL);
  return std::string(":CompileOptions:" +
         base::IntToString(GetCompileOptions())) +
         ShGetBuiltInResourcesString(compiler_);
}

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

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

ShaderTranslator::~ShaderTranslator() {
  FOR_EACH_OBSERVER(DestructionObserver,
                    destruction_observers_,
                    OnDestruct(this));

  if (compiler_ != NULL)
    ShDestruct(compiler_);
}

}  // namespace gles2
}  // namespace gpu