// Copyright (c) 2010 The Chromium OS 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 <stdio.h>

#include "main.h"
#include "testbase.h"
#include "utils.h"


namespace glbench {

const float kScreenScaleFactor = 1e6f * (WINDOW_WIDTH * WINDOW_HEIGHT) /
    (1280.f * 768);

class WindowManagerCompositingTest : public TestBase {
 public:
  WindowManagerCompositingTest(bool scissor)
      : scissor_(scissor),
      compositing_background_program_(0),
      compositing_foreground_program_(0) {}
  virtual ~WindowManagerCompositingTest() {}
  virtual bool TestFunc(uint64_t iterations);
  virtual bool Run();
  virtual const char* Name() const { return "compositing"; }
  virtual bool IsDrawTest() const { return true; }
  virtual const char* Unit() const { return "1280x768_fps"; }

  void InitializeCompositing();
  void TeardownCompositing();
  void InitBaseTexture();
  void UpdateTexture();
  void LoadTexture();

 private:
  bool scissor_;
  uint32_t texture_base_[WINDOW_HEIGHT*WINDOW_WIDTH];
  uint32_t texture_update_[WINDOW_HEIGHT*WINDOW_WIDTH];
  GLuint compositing_textures_[5];
  GLuint compositing_background_program_;
  GLuint compositing_foreground_program_;
  DISALLOW_COPY_AND_ASSIGN(WindowManagerCompositingTest);
};

TestBase* GetWindowManagerCompositingTest(bool enable_scissor) {
  return new WindowManagerCompositingTest(enable_scissor);
}

bool WindowManagerCompositingTest::Run() {
  const char* testname = "compositing";
  if (scissor_) {
    glScissor(0, 0, 1, 1);
    glEnable(GL_SCISSOR_TEST);
    testname = "compositing_no_fill";
  }
  InitializeCompositing();
  RunTest(this, testname, kScreenScaleFactor, WINDOW_WIDTH, WINDOW_HEIGHT, true);
  TeardownCompositing();
  return true;
}

bool WindowManagerCompositingTest::TestFunc(uint64_t iterations) {
  for (uint64_t i = 0 ; i < iterations; ++i) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Draw the background
    glDisable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);
    // We have to blend three textures, but we use multi-texture for this
    // blending, not fb blend, to avoid the external memory traffic
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, compositing_textures_[0]);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, compositing_textures_[1]);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, compositing_textures_[2]);
    // Use the right shader
    glUseProgram(compositing_background_program_);
    // Draw the quad
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Use the right shader
    glUseProgram(compositing_foreground_program_);

    // Compositing is blending, so we shall blend.
    glEnable(GL_BLEND);
    // Depth test is on for window occlusion
    glEnable(GL_DEPTH_TEST);

    // Draw window number one
    // This update acts like a chrome webkit sw rendering update.
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, compositing_textures_[3]);
    UpdateTexture();
    // TODO(papakipos): this LoadTexture is likely doing more CPU memory copies
    // than we would like.
    LoadTexture();
    // TODO(papakipos): add color interpolation here, and modulate
    // texture against it.
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Draw window number two
    // This is a static window, so we don't update it.
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, compositing_textures_[4]);
    // TODO(papakipos): add color interpolation here, and modulate
    // texture against it.
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  }
  return true;
}

const char *kBasicTextureVertexShader =
    "attribute vec4 c1;"
    "attribute vec4 c2;"
    "varying vec4 v1;"
    "void main() {"
    "    gl_Position = c1;"
    "    v1 = c2;"
    "}";

const char *kBasicTextureFragmentShader =
    "uniform sampler2D texture_sampler;"
    "varying vec4 v1;"
    "void main() {"
    "    gl_FragColor = texture2D(texture_sampler, v1.st);"
    "}";

GLuint BasicTextureShaderProgram(GLuint vertex_buffer, GLuint texture_buffer) {
  GLuint program = InitShaderProgram(kBasicTextureVertexShader,
                                     kBasicTextureFragmentShader);

  // Set up the texture sampler
  int textureSampler = glGetUniformLocation(program, "texture_sampler");
  glUniform1i(textureSampler, 0);

  // Set up vertex attribute
  int attribute_index = glGetAttribLocation(program, "c1");
  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  // Set up texture attribute
  attribute_index = glGetAttribLocation(program, "c2");
  glBindBuffer(GL_ARRAY_BUFFER, texture_buffer);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  return program;
}

const char *kDoubleTextureBlendVertexShader =
    "attribute vec4 c1;"
    "attribute vec4 c2;"
    "attribute vec4 c3;"
    "varying vec4 v1;"
    "varying vec4 v2;"
    "void main() {"
    "    gl_Position = c1;"
    "    v1 = c2;"
    "    v2 = c3;"
    "}";

const char *kDoubleTextureBlendFragmentShader =
    "uniform sampler2D texture_sampler_0;"
    "uniform sampler2D texture_sampler_1;"
    "varying vec4 v1;"
    "varying vec4 v2;"
    "void main() {"
    "    vec4 one = texture2D(texture_sampler_0, v1.st);"
    "    vec4 two = texture2D(texture_sampler_1, v2.st);"
    "    gl_FragColor = mix(one, two, 0.5);"
    "}";

// This shader blends the three textures
GLuint DoubleTextureBlendShaderProgram(GLuint vertex_buffer,
                                       GLuint texture_buffer_0,
                                       GLuint texture_buffer_1) {
  GLuint program = InitShaderProgram(kDoubleTextureBlendVertexShader,
                                     kDoubleTextureBlendFragmentShader);
  // Set up the texture sampler
  int textureSampler0 = glGetUniformLocation(program, "texture_sampler_0");
  glUniform1i(textureSampler0, 0);
  int textureSampler1 = glGetUniformLocation(program, "texture_sampler_1");
  glUniform1i(textureSampler1, 1);

  // Set up vertex attribute
  int attribute_index = glGetAttribLocation(program, "c1");
  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  // Set up texture attributes
  attribute_index = glGetAttribLocation(program, "c2");
  glBindBuffer(GL_ARRAY_BUFFER, texture_buffer_0);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  attribute_index = glGetAttribLocation(program, "c3");
  glBindBuffer(GL_ARRAY_BUFFER, texture_buffer_1);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  return program;
}

const char *triple_texture_blend_vertex_shader =
"attribute vec4 c1;"
"attribute vec4 c2;"
"attribute vec4 c3;"
"attribute vec4 c4;"
"varying vec4 v1;"
"varying vec4 v2;"
"varying vec4 v3;"
"void main() {"
"    gl_Position = c1;"
"    v1 = c2;"
"    v2 = c3;"
"    v3 = c4;"
"}";

const char *triple_texture_blend_fragment_shader =
"uniform sampler2D texture_sampler_0;"
"uniform sampler2D texture_sampler_1;"
"uniform sampler2D texture_sampler_2;"
"varying vec4 v1;"
"varying vec4 v2;"
"varying vec4 v3;"
"void main() {"
"    vec4 one = texture2D(texture_sampler_0, v1.st);"
"    vec4 two = texture2D(texture_sampler_1, v2.st);"
"    vec4 three = texture2D(texture_sampler_2, v3.st);"
"    gl_FragColor = mix(mix(one, two, 0.5), three, 0.5);"
"}";

// This shader blends the three textures
GLuint TripleTextureBlendShaderProgram(GLuint vertex_buffer,
                                              GLuint texture_buffer_0,
                                              GLuint texture_buffer_1,
                                              GLuint texture_buffer_2) {
  GLuint program =
    InitShaderProgram(triple_texture_blend_vertex_shader,
                      triple_texture_blend_fragment_shader);

  // Set up the texture sampler
  int textureSampler0 = glGetUniformLocation(program, "texture_sampler_0");
  glUniform1i(textureSampler0, 0);
  int textureSampler1 = glGetUniformLocation(program, "texture_sampler_1");
  glUniform1i(textureSampler1, 1);
  int textureSampler2 = glGetUniformLocation(program, "texture_sampler_2");
  glUniform1i(textureSampler2, 2);

  // Set up vertex attribute
  int attribute_index = glGetAttribLocation(program, "c1");
  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  // Set up texture attributes
  attribute_index = glGetAttribLocation(program, "c2");
  glBindBuffer(GL_ARRAY_BUFFER, texture_buffer_0);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  attribute_index = glGetAttribLocation(program, "c3");
  glBindBuffer(GL_ARRAY_BUFFER, texture_buffer_1);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  attribute_index = glGetAttribLocation(program, "c4");
  glBindBuffer(GL_ARRAY_BUFFER, texture_buffer_2);
  glVertexAttribPointer(attribute_index, 2, GL_FLOAT, GL_FALSE, 0, NULL);
  glEnableVertexAttribArray(attribute_index);

  return program;
}

void WindowManagerCompositingTest::InitializeCompositing() {
  InitBaseTexture();

  glClearColor(0.f, 0.f, 0.f, 0.f);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glDepthFunc(GL_LEQUAL);

  glGenTextures(5, compositing_textures_);
  glActiveTexture(GL_TEXTURE0);
  for (int i = 0; i < 5; i++) {
    glBindTexture(GL_TEXTURE_2D, compositing_textures_[i]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                    GL_LINEAR);
  }

  // Set up the vertex arrays for drawing textured quads later on.
  GLfloat buffer_vertex[8] = {
    -1.f, -1.f,
    1.f,  -1.f,
    -1.f, 1.f,
    1.f,  1.f,
  };
  GLuint vbo_vertex = SetupVBO(GL_ARRAY_BUFFER,
                               sizeof(buffer_vertex), buffer_vertex);

  GLfloat buffer_texture[8] = {
    0.f, 0.f,
    1.f, 0.f,
    0.f, 1.f,
    1.f, 1.f,
  };
  GLuint vbo_texture = SetupVBO(GL_ARRAY_BUFFER,
                                sizeof(buffer_texture), buffer_texture);

  // Set up the static background textures.
  UpdateTexture();
  UpdateTexture();
  UpdateTexture();
  // Load these textures into bound texture ids and keep using them
  // from there to avoid having to reload this texture every frame
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, compositing_textures_[0]);
  LoadTexture();
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, compositing_textures_[1]);
  LoadTexture();
  glActiveTexture(GL_TEXTURE2);
  glBindTexture(GL_TEXTURE_2D, compositing_textures_[2]);
  LoadTexture();

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, compositing_textures_[3]);
  UpdateTexture();
  LoadTexture();

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, compositing_textures_[4]);
  UpdateTexture();
  LoadTexture();

  // Set up vertex & fragment shaders.
  compositing_background_program_ =
      TripleTextureBlendShaderProgram(vbo_vertex,
                                      vbo_texture, vbo_texture, vbo_texture);
  compositing_foreground_program_ =
      BasicTextureShaderProgram(vbo_vertex, vbo_texture);
  if (!compositing_background_program_ || !compositing_foreground_program_) {
    printf("# Warning: Could not set up compositing shader.\n");
  }
}

void WindowManagerCompositingTest::TeardownCompositing() {
  glDeleteProgram(compositing_background_program_);
  glDeleteProgram(compositing_foreground_program_);
}

void WindowManagerCompositingTest::InitBaseTexture() {
  for (int y = 0; y < WINDOW_HEIGHT; y++) {
    for (int x = 0; x < WINDOW_WIDTH; x++) {
      // This color is gray, half alpha.
      texture_base_[y*WINDOW_WIDTH+x] = 0x80808080;
    }
  }
}

// UpdateTexture simulates Chrome updating tab contents.
// We cause a bunch of read and write cpu memory bandwidth.
// It's a very rough approximation.
void WindowManagerCompositingTest::UpdateTexture() {
  memcpy(texture_update_, texture_base_, sizeof(texture_base_));
}

void WindowManagerCompositingTest::LoadTexture() {
  // Use GL_RGBA for compatibility with GLES2.0.
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
               WINDOW_WIDTH, WINDOW_HEIGHT, 0,
               GL_RGBA, GL_UNSIGNED_BYTE, texture_update_);
}

} // namespace glbench