// Copyright 2014 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/gles2_cmd_clear_framebuffer.h"

#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
#include "ui/gfx/geometry/size.h"

namespace {

#define SHADER(src)            \
  "#ifdef GL_ES\n"             \
  "precision mediump float;\n" \
  "#endif\n" #src

const char* g_vertex_shader_source = {
  SHADER(
    uniform float u_clear_depth;
    attribute vec4 a_position;
    void main(void) {
      gl_Position = vec4(a_position.x, a_position.y, u_clear_depth, 1.0);
    }
  ),
};

const char* g_fragment_shader_source = {
  SHADER(
    uniform vec4 u_clear_color;
    void main(void) {
      gl_FragColor = u_clear_color;
    }
  ),
};

void CompileShader(GLuint shader, const char* shader_source) {
  glShaderSource(shader, 1, &shader_source, 0);
  glCompileShader(shader);
#if DCHECK_IS_ON()
  GLint compile_status = GL_FALSE;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
  if (GL_TRUE != compile_status) {
    char buffer[1024];
    GLsizei length = 0;
    glGetShaderInfoLog(shader, sizeof(buffer), &length, buffer);
    std::string log(buffer, length);
    DLOG(ERROR) << "Error compiling shader: " << log;
    DLOG(ERROR) << "Shader compilation failure.";
  }
#endif
}

}  // namespace

namespace gpu {
namespace gles2 {

ClearFramebufferResourceManager::ClearFramebufferResourceManager(
    const gles2::GLES2Decoder* decoder)
    : initialized_(false), program_(0u), buffer_id_(0u) {
  Initialize(decoder);
}

ClearFramebufferResourceManager::~ClearFramebufferResourceManager() {
  Destroy();
  DCHECK(!buffer_id_);
}

void ClearFramebufferResourceManager::Initialize(
    const gles2::GLES2Decoder* decoder) {
  static_assert(
      kVertexPositionAttrib == 0u,
      "kVertexPositionAttrib must be 0");
  DCHECK(!buffer_id_);

  glGenBuffersARB(1, &buffer_id_);
  glBindBuffer(GL_ARRAY_BUFFER, buffer_id_);
  const GLfloat kQuadVertices[] = {-1.0f, -1.0f,
                                    1.0f, -1.0f,
                                    1.0f,  1.0f,
                                   -1.0f,  1.0f};
  glBufferData(
      GL_ARRAY_BUFFER, sizeof(kQuadVertices), kQuadVertices, GL_STATIC_DRAW);
  decoder->RestoreBufferBindings();
  initialized_ = true;
}

void ClearFramebufferResourceManager::Destroy() {
  if (!initialized_)
    return;

  glDeleteProgram(program_);
  glDeleteBuffersARB(1, &buffer_id_);
  buffer_id_ = 0;
}

void ClearFramebufferResourceManager::ClearFramebuffer(
    const gles2::GLES2Decoder* decoder,
    const gfx::Size& max_viewport_size,
    GLbitfield mask,
    GLfloat clear_color_red,
    GLfloat clear_color_green,
    GLfloat clear_color_blue,
    GLfloat clear_color_alpha,
    GLfloat clear_depth_value,
    GLint clear_stencil_value) {
  if (!initialized_) {
    DLOG(ERROR) << "Uninitialized manager.";
    return;
  }

  if (!program_) {
    program_ = glCreateProgram();
    GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    CompileShader(vertex_shader, g_vertex_shader_source);
    glAttachShader(program_, vertex_shader);
    GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    CompileShader(fragment_shader, g_fragment_shader_source);
    glAttachShader(program_, fragment_shader);
    glBindAttribLocation(program_, kVertexPositionAttrib, "a_position");
    glLinkProgram(program_);
#if DCHECK_IS_ON()
    GLint linked = GL_FALSE;
    glGetProgramiv(program_, GL_LINK_STATUS, &linked);
    if (GL_TRUE != linked)
      DLOG(ERROR) << "Program link failure.";
#endif
    depth_handle_ = glGetUniformLocation(program_, "u_clear_depth");
    color_handle_ = glGetUniformLocation(program_, "u_clear_color");
    glDeleteShader(fragment_shader);
    glDeleteShader(vertex_shader);
  }
  glUseProgram(program_);

#if DCHECK_IS_ON()
  glValidateProgram(program_);
  GLint validation_status = GL_FALSE;
  glGetProgramiv(program_, GL_VALIDATE_STATUS, &validation_status);
  if (GL_TRUE != validation_status)
    DLOG(ERROR) << "Invalid shader.";
#endif

  decoder->ClearAllAttributes();
  glEnableVertexAttribArray(kVertexPositionAttrib);

  glBindBuffer(GL_ARRAY_BUFFER, buffer_id_);
  glVertexAttribPointer(kVertexPositionAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);

  glUniform1f(depth_handle_, clear_depth_value);
  glUniform4f(color_handle_, clear_color_red, clear_color_green,
              clear_color_blue, clear_color_alpha);

  if (!(mask & GL_COLOR_BUFFER_BIT)) {
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  }

  if (mask & GL_DEPTH_BUFFER_BIT) {
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
  } else {
    glDisable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);
  }

  if (mask & GL_STENCIL_BUFFER_BIT) {
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, clear_stencil_value, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
  } else {
    glDisable(GL_STENCIL_TEST);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0);
  }

  glDisable(GL_CULL_FACE);
  glDisable(GL_BLEND);
  glDisable(GL_POLYGON_OFFSET_FILL);

  glViewport(0, 0, max_viewport_size.width(), max_viewport_size.height());
  glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

  decoder->RestoreAllAttributes();
  decoder->RestoreProgramBindings();
  decoder->RestoreBufferBindings();
  decoder->RestoreGlobalState();
}

}  // namespace gles2
}  // namespace gpu
