| // Copyright (c) 2013 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 <GLES2/gl2.h> |
| #include <math.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.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 kFovY = 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 Graphics3DInstance : public pp::Instance { |
| public: |
| explicit Graphics3DInstance(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) {} |
| |
| virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { |
| return true; |
| } |
| |
| virtual void DidChangeView(const pp::View& view) { |
| // Pepper specifies dimensions in DIPs (device-independent pixels). To |
| // generate a context that is at device-pixel resolution on HiDPI devices, |
| // scale the dimensions by view.GetDeviceScale(). |
| int32_t new_width = view.GetRect().width() * view.GetDeviceScale(); |
| int32_t new_height = view.GetRect().height() * view.GetDeviceScale(); |
| |
| 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_; |
| glhPerspectivef2(&mvp[0], kFovY, 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(&Graphics3DInstance::MainLoop)); |
| } |
| |
| pp::CompletionCallbackFactory<Graphics3DInstance> 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_; |
| }; |
| |
| class Graphics3DModule : public pp::Module { |
| public: |
| Graphics3DModule() : pp::Module() {} |
| virtual ~Graphics3DModule() {} |
| |
| virtual pp::Instance* CreateInstance(PP_Instance instance) { |
| return new Graphics3DInstance(instance); |
| } |
| }; |
| |
| namespace pp { |
| Module* CreateModule() { return new Graphics3DModule(); } |
| } // namespace pp |