chromium/native_client_sdk/src/gonacl_appengine/src/cube/cube.cc

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

#include <GLES2/gl2.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "common/fps.h"
#include "matrix.h"
#include "ppapi/cpp/graphics_3d.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
#include "ppapi/cpp/var_array.h"
#include "ppapi/lib/gl/gles2/gl2ext_ppapi.h"
#include "ppapi/utility/completion_callback_factory.h"

#ifdef WIN32
#undef PostMessage
// Allow 'this' in initializer list
#pragma warning(disable : 4355)
#endif

extern const uint8_t kRLETextureData[];
extern const size_t kRLETextureDataLength;

namespace {

const float kMinFovY = 45.0f;
const float kZNear = 1.0f;
const float kZFar = 10.0f;
const float kCameraZ = -4.0f;
const float kXAngleDelta = 2.0f;
const float kYAngleDelta = 0.5f;

const size_t kTextureDataLength = 128 * 128 * 3;  // 128x128, 3 Bytes/pixel.

// The decompressed data is written here.
uint8_t g_texture_data[kTextureDataLength];

void DecompressTexture() {
  // The image is first encoded with a very simple RLE scheme:
  //   <value0> <count0> <value1> <count1> ...
  // Because a <count> of 0 is useless, we use it to represent 256.
  //
  // It is then Base64 encoded to make it use only printable characters (it
  // stores more easily in a source file that way).
  //
  // To decompress, we have to reverse the process.
  static const uint8_t kBase64Decode[256] = {
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62,  0,  0,  0, 63,
   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,  0,  0,  0,  0,  0,  0,
    0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,  0,  0,  0,  0,
    0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
  };
  const uint8_t* input = &kRLETextureData[0];
  const uint8_t* const input_end = &kRLETextureData[kRLETextureDataLength];
  uint8_t* output = &g_texture_data[0];
#ifndef NDEBUG
  const uint8_t* const output_end = &g_texture_data[kTextureDataLength];
#endif

  uint8_t decoded[4];
  int decoded_count = 0;

  while (input < input_end || decoded_count > 0) {
    if (decoded_count < 2) {
      assert(input + 4 <= input_end);
      // Grab four base-64 encoded (6-bit) bytes.
      uint32_t data = 0;
      data |= (kBase64Decode[*input++] << 18);
      data |= (kBase64Decode[*input++] << 12);
      data |= (kBase64Decode[*input++] <<  6);
      data |= (kBase64Decode[*input++]      );
      // And decode it to 3 (8-bit) bytes.
      decoded[decoded_count++] = (data >> 16) & 0xff;
      decoded[decoded_count++] = (data >>  8) & 0xff;
      decoded[decoded_count++] = (data      ) & 0xff;

      // = is the base64 end marker. Remove decoded bytes if we see any.
      if (input[-1] == '=') decoded_count--;
      if (input[-2] == '=') decoded_count--;
    }

    int value = decoded[0];
    int count = decoded[1];
    decoded_count -= 2;
    // Move the other decoded bytes (if any) down.
    decoded[0] = decoded[2];
    decoded[1] = decoded[3];

    // Expand the RLE data.
    if (count == 0)
      count = 256;
    assert(output <= output_end);
    memset(output, value, count);
    output += count;
  }
  assert(output == output_end);
}

GLuint CompileShader(GLenum type, const char* data) {
  GLuint shader = glCreateShader(type);
  glShaderSource(shader, 1, &data, NULL);
  glCompileShader(shader);

  GLint compile_status;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
  if (compile_status != GL_TRUE) {
    // Shader failed to compile, let's see what the error is.
    char buffer[1024];
    GLsizei length;
    glGetShaderInfoLog(shader, sizeof(buffer), &length, &buffer[0]);
    fprintf(stderr, "Shader failed to compile: %s\n", buffer);
    return 0;
  }

  return shader;
}

GLuint LinkProgram(GLuint frag_shader, GLuint vert_shader) {
  GLuint program = glCreateProgram();
  glAttachShader(program, frag_shader);
  glAttachShader(program, vert_shader);
  glLinkProgram(program);

  GLint link_status;
  glGetProgramiv(program, GL_LINK_STATUS, &link_status);
  if (link_status != GL_TRUE) {
    // Program failed to link, let's see what the error is.
    char buffer[1024];
    GLsizei length;
    glGetProgramInfoLog(program, sizeof(buffer), &length, &buffer[0]);
    fprintf(stderr, "Program failed to link: %s\n", buffer);
    return 0;
  }

  return program;
}

const char kFragShaderSource[] =
    "precision mediump float;\n"
    "varying vec3 v_color;\n"
    "varying vec2 v_texcoord;\n"
    "uniform sampler2D u_texture;\n"
    "void main() {\n"
    "  gl_FragColor = texture2D(u_texture, v_texcoord);\n"
    "  gl_FragColor += vec4(v_color, 1);\n"
    "}\n";

const char kVertexShaderSource[] =
    "uniform mat4 u_mvp;\n"
    "attribute vec2 a_texcoord;\n"
    "attribute vec3 a_color;\n"
    "attribute vec4 a_position;\n"
    "varying vec3 v_color;\n"
    "varying vec2 v_texcoord;\n"
    "void main() {\n"
    "  gl_Position = u_mvp * a_position;\n"
    "  v_color = a_color;\n"
    "  v_texcoord = a_texcoord;\n"
    "}\n";

struct Vertex {
  float loc[3];
  float color[3];
  float tex[2];
};

const Vertex kCubeVerts[24] = {
  // +Z (red arrow, black tip)
  {{-1.0, -1.0, +1.0}, {0.0, 0.0, 0.0}, {1.0, 0.0}},
  {{+1.0, -1.0, +1.0}, {0.0, 0.0, 0.0}, {0.0, 0.0}},
  {{+1.0, +1.0, +1.0}, {0.5, 0.0, 0.0}, {0.0, 1.0}},
  {{-1.0, +1.0, +1.0}, {0.5, 0.0, 0.0}, {1.0, 1.0}},

  // +X (green arrow, black tip)
  {{+1.0, -1.0, -1.0}, {0.0, 0.0, 0.0}, {1.0, 0.0}},
  {{+1.0, +1.0, -1.0}, {0.0, 0.0, 0.0}, {0.0, 0.0}},
  {{+1.0, +1.0, +1.0}, {0.0, 0.5, 0.0}, {0.0, 1.0}},
  {{+1.0, -1.0, +1.0}, {0.0, 0.5, 0.0}, {1.0, 1.0}},

  // +Y (blue arrow, black tip)
  {{-1.0, +1.0, -1.0}, {0.0, 0.0, 0.0}, {1.0, 0.0}},
  {{-1.0, +1.0, +1.0}, {0.0, 0.0, 0.0}, {0.0, 0.0}},
  {{+1.0, +1.0, +1.0}, {0.0, 0.0, 0.5}, {0.0, 1.0}},
  {{+1.0, +1.0, -1.0}, {0.0, 0.0, 0.5}, {1.0, 1.0}},

  // -Z (red arrow, red tip)
  {{+1.0, +1.0, -1.0}, {0.0, 0.0, 0.0}, {1.0, 1.0}},
  {{-1.0, +1.0, -1.0}, {0.0, 0.0, 0.0}, {0.0, 1.0}},
  {{-1.0, -1.0, -1.0}, {1.0, 0.0, 0.0}, {0.0, 0.0}},
  {{+1.0, -1.0, -1.0}, {1.0, 0.0, 0.0}, {1.0, 0.0}},

  // -X (green arrow, green tip)
  {{-1.0, +1.0, +1.0}, {0.0, 0.0, 0.0}, {1.0, 1.0}},
  {{-1.0, -1.0, +1.0}, {0.0, 0.0, 0.0}, {0.0, 1.0}},
  {{-1.0, -1.0, -1.0}, {0.0, 1.0, 0.0}, {0.0, 0.0}},
  {{-1.0, +1.0, -1.0}, {0.0, 1.0, 0.0}, {1.0, 0.0}},

  // -Y (blue arrow, blue tip)
  {{+1.0, -1.0, +1.0}, {0.0, 0.0, 0.0}, {1.0, 1.0}},
  {{+1.0, -1.0, -1.0}, {0.0, 0.0, 0.0}, {0.0, 1.0}},
  {{-1.0, -1.0, -1.0}, {0.0, 0.0, 1.0}, {0.0, 0.0}},
  {{-1.0, -1.0, +1.0}, {0.0, 0.0, 1.0}, {1.0, 0.0}},
};

const GLubyte kCubeIndexes[36] = {
   2,  1,  0,  3,  2,  0,
   6,  5,  4,  7,  6,  4,
  10,  9,  8, 11, 10,  8,
  14, 13, 12, 15, 14, 12,
  18, 17, 16, 19, 18, 16,
  22, 21, 20, 23, 22, 20,
};

}  // namespace


class CubeInstance : public pp::Instance {
 public:
  explicit CubeInstance(PP_Instance instance)
      : pp::Instance(instance),
        callback_factory_(this),
        width_(0),
        height_(0),
        frag_shader_(0),
        vertex_shader_(0),
        program_(0),
        texture_loc_(0),
        position_loc_(0),
        color_loc_(0),
        mvp_loc_(0),
        x_angle_(0),
        y_angle_(0),
        animating_(true) {
    FpsInit(&fps_state_);
  }

  virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
    return true;
  }

  virtual void DidChangeView(const pp::View& view) {
    int32_t new_width = view.GetRect().width();
    int32_t new_height = view.GetRect().height();

    if (context_.is_null()) {
      if (!InitGL(new_width, new_height)) {
        // failed.
        return;
      }

      InitShaders();
      InitBuffers();
      InitTexture();
      MainLoop(0);
    } else {
      // Resize the buffers to the new size of the module.
      int32_t result = context_.ResizeBuffers(new_width, new_height);
      if (result < 0) {
        fprintf(stderr,
                "Unable to resize buffers to %d x %d!\n",
                new_width,
                new_height);
        return;
      }
    }

    width_ = new_width;
    height_ = new_height;
    glViewport(0, 0, width_, height_);
  }

  virtual void HandleMessage(const pp::Var& message) {
    // A bool message sets whether the cube is animating or not.
    if (message.is_bool()) {
      animating_ = message.AsBool();
      return;
    }

    // An array message sets the current x and y rotation.
    if (!message.is_array()) {
      fprintf(stderr, "Expected array message.\n");
      return;
    }

    pp::VarArray array(message);
    if (array.GetLength() != 2) {
      fprintf(stderr, "Expected array of length 2.\n");
      return;
    }

    pp::Var x_angle_var = array.Get(0);
    if (x_angle_var.is_int()) {
      x_angle_ = x_angle_var.AsInt();
    } else if (x_angle_var.is_double()) {
      x_angle_ = x_angle_var.AsDouble();
    } else {
      fprintf(stderr, "Expected value to be an int or double.\n");
    }

    pp::Var y_angle_var = array.Get(1);
    if (y_angle_var.is_int()) {
      y_angle_ = y_angle_var.AsInt();
    } else if (y_angle_var.is_double()) {
      y_angle_ = y_angle_var.AsDouble();
    } else {
      fprintf(stderr, "Expected value to be an int or double.\n");
    }
  }

 private:
  bool InitGL(int32_t new_width, int32_t new_height) {
    if (!glInitializePPAPI(pp::Module::Get()->get_browser_interface())) {
      fprintf(stderr, "Unable to initialize GL PPAPI!\n");
      return false;
    }

    const int32_t attrib_list[] = {
      PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
      PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24,
      PP_GRAPHICS3DATTRIB_WIDTH, new_width,
      PP_GRAPHICS3DATTRIB_HEIGHT, new_height,
      PP_GRAPHICS3DATTRIB_NONE
    };

    context_ = pp::Graphics3D(this, attrib_list);
    if (!BindGraphics(context_)) {
      fprintf(stderr, "Unable to bind 3d context!\n");
      context_ = pp::Graphics3D();
      glSetCurrentContextPPAPI(0);
      return false;
    }

    glSetCurrentContextPPAPI(context_.pp_resource());
    return true;
  }

  void InitShaders() {
    frag_shader_ = CompileShader(GL_FRAGMENT_SHADER, kFragShaderSource);
    if (!frag_shader_)
      return;

    vertex_shader_ = CompileShader(GL_VERTEX_SHADER, kVertexShaderSource);
    if (!vertex_shader_)
      return;

    program_ = LinkProgram(frag_shader_, vertex_shader_);
    if (!program_)
      return;

    texture_loc_ = glGetUniformLocation(program_, "u_texture");
    position_loc_ = glGetAttribLocation(program_, "a_position");
    texcoord_loc_ = glGetAttribLocation(program_, "a_texcoord");
    color_loc_ = glGetAttribLocation(program_, "a_color");
    mvp_loc_ = glGetUniformLocation(program_, "u_mvp");
  }

  void InitBuffers() {
    glGenBuffers(1, &vertex_buffer_);
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
    glBufferData(GL_ARRAY_BUFFER, sizeof(kCubeVerts), &kCubeVerts[0],
                 GL_STATIC_DRAW);

    glGenBuffers(1, &index_buffer_);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kCubeIndexes),
                 &kCubeIndexes[0], GL_STATIC_DRAW);
  }

  void InitTexture() {
    DecompressTexture();
    glGenTextures(1, &texture_);
    glBindTexture(GL_TEXTURE_2D, texture_);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_RGB,
                 128,
                 128,
                 0,
                 GL_RGB,
                 GL_UNSIGNED_BYTE,
                 &g_texture_data[0]);
  }

  void Animate() {
    if (animating_) {
      x_angle_ = fmod(360.0f + x_angle_ + kXAngleDelta, 360.0f);
      y_angle_ = fmod(360.0f + y_angle_ + kYAngleDelta, 360.0f);

      // Send new values to JavaScript.
      pp::VarArray array;
      array.SetLength(2);
      array.Set(0, x_angle_);
      array.Set(1, y_angle_);
      PostMessage(array);
    }
  }

  void Render() {
    glClearColor(0.5, 0.5, 0.5, 1);
    glClearDepthf(1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    //set what program to use
    glUseProgram(program_);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture_);
    glUniform1i(texture_loc_, 0);

    //create our perspective matrix
    float mvp[16];
    float trs[16];
    float rot[16];

    identity_matrix(mvp);
    const float aspect_ratio = static_cast<float>(width_) / height_;
    float fovY = kMinFovY / aspect_ratio;
    if (fovY < kMinFovY)
      fovY = kMinFovY;

    glhPerspectivef2(&mvp[0], fovY, aspect_ratio, kZNear, kZFar);

    translate_matrix(0, 0, kCameraZ, trs);
    rotate_matrix(x_angle_, y_angle_, 0.0f, rot);
    multiply_matrix(trs, rot, trs);
    multiply_matrix(mvp, trs, mvp);
    glUniformMatrix4fv(mvp_loc_, 1, GL_FALSE, mvp);

    //define the attributes of the vertex
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
    glVertexAttribPointer(position_loc_,
                          3,
                          GL_FLOAT,
                          GL_FALSE,
                          sizeof(Vertex),
                          reinterpret_cast<void*>(offsetof(Vertex, loc)));
    glEnableVertexAttribArray(position_loc_);
    glVertexAttribPointer(color_loc_,
                          3,
                          GL_FLOAT,
                          GL_FALSE,
                          sizeof(Vertex),
                          reinterpret_cast<void*>(offsetof(Vertex, color)));
    glEnableVertexAttribArray(color_loc_);
    glVertexAttribPointer(texcoord_loc_,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          sizeof(Vertex),
                          reinterpret_cast<void*>(offsetof(Vertex, tex)));
    glEnableVertexAttribArray(texcoord_loc_);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, 0);
  }

  void MainLoop(int32_t) {
    Animate();
    Render();
    context_.SwapBuffers(
        callback_factory_.NewCallback(&CubeInstance::MainLoop));

    double fps;
    if (FpsStep(&fps_state_, &fps))
      PostMessage(fps);
  }

  pp::CompletionCallbackFactory<CubeInstance> callback_factory_;
  pp::Graphics3D context_;
  int32_t width_;
  int32_t height_;
  GLuint frag_shader_;
  GLuint vertex_shader_;
  GLuint program_;
  GLuint vertex_buffer_;
  GLuint index_buffer_;
  GLuint texture_;

  GLuint texture_loc_;
  GLuint position_loc_;
  GLuint texcoord_loc_;
  GLuint color_loc_;
  GLuint mvp_loc_;

  float x_angle_;
  float y_angle_;
  bool animating_;
  FpsState fps_state_;
};

class CubeModule : public pp::Module {
 public:
  CubeModule() : pp::Module() {}
  virtual ~CubeModule() {}

  virtual pp::Instance* CreateInstance(PP_Instance instance) {
    return new CubeInstance(instance);
  }
};

namespace pp {
Module* CreateModule() { return new CubeModule(); }
}  // namespace pp