// Copyright 2011 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 "cc/output/program_binding.h"

#include "base/debug/trace_event.h"
#include "cc/output/geometry_binding.h"
#include "cc/output/gl_renderer.h"  // For the GLC() macro.
#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
#include "third_party/khronos/GLES2/gl2.h"

using WebKit::WebGraphicsContext3D;

namespace cc {

ProgramBindingBase::ProgramBindingBase()
    : program_(0),
      vertex_shader_id_(0),
      fragment_shader_id_(0),
      initialized_(false) {}

ProgramBindingBase::~ProgramBindingBase() {
  // If you hit these asserts, you initialized but forgot to call Cleanup().
  DCHECK(!program_);
  DCHECK(!vertex_shader_id_);
  DCHECK(!fragment_shader_id_);
  DCHECK(!initialized_);
}

void ProgramBindingBase::Init(WebGraphicsContext3D* context,
                              const std::string& vertex_shader,
                              const std::string& fragment_shader) {
  TRACE_EVENT0("cc", "ProgramBindingBase::init");
  vertex_shader_id_ = LoadShader(context, GL_VERTEX_SHADER, vertex_shader);
  if (!vertex_shader_id_) {
    if (!IsContextLost(context))
      LOG(ERROR) << "Failed to create vertex shader";
    return;
  }

  fragment_shader_id_ =
      LoadShader(context, GL_FRAGMENT_SHADER, fragment_shader);
  if (!fragment_shader_id_) {
    GLC(context, context->deleteShader(vertex_shader_id_));
    vertex_shader_id_ = 0;
    if (!IsContextLost(context))
      LOG(ERROR) << "Failed to create fragment shader";
    return;
  }

  program_ =
      CreateShaderProgram(context, vertex_shader_id_, fragment_shader_id_);
  DCHECK(program_ || IsContextLost(context));
}

void ProgramBindingBase::Link(WebGraphicsContext3D* context) {
  GLC(context, context->linkProgram(program_));
  CleanupShaders(context);
  if (!program_)
    return;
#ifndef NDEBUG
  int linked = 0;
  GLC(context, context->getProgramiv(program_, GL_LINK_STATUS, &linked));
  if (!linked) {
    if (!IsContextLost(context))
      LOG(ERROR) << "Failed to link shader program";
    GLC(context, context->deleteProgram(program_));
  }
#endif
}

void ProgramBindingBase::Cleanup(WebGraphicsContext3D* context) {
  initialized_ = false;
  if (!program_)
    return;

  DCHECK(context);
  GLC(context, context->deleteProgram(program_));
  program_ = 0;

  CleanupShaders(context);
}

unsigned ProgramBindingBase::LoadShader(WebGraphicsContext3D* context,
                                        unsigned type,
                                        const std::string& shader_source) {
  unsigned shader = context->createShader(type);
  if (!shader)
    return 0;
  GLC(context, context->shaderSource(shader, shader_source.data()));
  GLC(context, context->compileShader(shader));
#ifndef NDEBUG
  int compiled = 0;
  GLC(context, context->getShaderiv(shader, GL_COMPILE_STATUS, &compiled));
  if (!compiled) {
    GLC(context, context->deleteShader(shader));
    return 0;
  }
#endif
  return shader;
}

unsigned ProgramBindingBase::CreateShaderProgram(WebGraphicsContext3D* context,
                                                 unsigned vertex_shader,
                                                 unsigned fragment_shader) {
  unsigned program_object = context->createProgram();
  if (!program_object) {
    if (!IsContextLost(context))
      LOG(ERROR) << "Failed to create shader program";
    return 0;
  }

  GLC(context, context->attachShader(program_object, vertex_shader));
  GLC(context, context->attachShader(program_object, fragment_shader));

  // Bind the common attrib locations.
  GLC(context,
      context->bindAttribLocation(program_object,
                                  GeometryBinding::PositionAttribLocation(),
                                  "a_position"));
  GLC(context,
      context->bindAttribLocation(program_object,
                                  GeometryBinding::TexCoordAttribLocation(),
                                  "a_texCoord"));
  GLC(context,
      context->bindAttribLocation(
          program_object,
          GeometryBinding::TriangleIndexAttribLocation(),
          "a_index"));

  return program_object;
}

void ProgramBindingBase::CleanupShaders(WebGraphicsContext3D* context) {
  if (vertex_shader_id_) {
    GLC(context, context->deleteShader(vertex_shader_id_));
    vertex_shader_id_ = 0;
  }
  if (fragment_shader_id_) {
    GLC(context, context->deleteShader(fragment_shader_id_));
    fragment_shader_id_ = 0;
  }
}

bool ProgramBindingBase::IsContextLost(WebGraphicsContext3D* context) {
  return (context->getGraphicsResetStatusARB() != GL_NO_ERROR);
}

}  // namespace cc