diff options
author | bradnelson@google.com <bradnelson@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-19 02:46:32 +0000 |
---|---|---|
committer | bradnelson@google.com <bradnelson@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-19 02:46:32 +0000 |
commit | 745945afe8e9fbfad7bb9ed442143172bf690073 (patch) | |
tree | 770e9c5f61e1204221fb4c2887cb863ad8b6688f /native_client_sdk/src/examples | |
parent | f0953ccbeb01a7a4ed1daa3cb84c2bc42adbed1f (diff) | |
download | chromium_src-745945afe8e9fbfad7bb9ed442143172bf690073.zip chromium_src-745945afe8e9fbfad7bb9ed442143172bf690073.tar.gz chromium_src-745945afe8e9fbfad7bb9ed442143172bf690073.tar.bz2 |
Adding reduced version of sdk at 1387 to chrome tree.
BUG=None
TEST=None
R=noelallen@google.com
TBR
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110822 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk/src/examples')
102 files changed, 10433 insertions, 0 deletions
diff --git a/native_client_sdk/src/examples/build.scons b/native_client_sdk/src/examples/build.scons new file mode 100644 index 0000000..f804d4b --- /dev/null +++ b/native_client_sdk/src/examples/build.scons @@ -0,0 +1,82 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import shutil +import sys + +""" +Build file for the NaCl SDK Examples + +This file runs all the scons files in the various example sub-directories. +Do not invoke this script directly, but instead use the scons or scons.bat +wrapper function. E.g. + +Linux or Mac: + ./scons [Options...] + +Windows: + scons.bat [Options...] +""" + +#------------------------------------------------------------------------------ +HELP_STRING = """ +=============================================================================== +Help for NaCl SDK Examples +=============================================================================== + +* cleaning: ./scons -c +* build a target: ./scons <target> +* clean a target: ./scons -c <target> + +Supported targets: + * fullscreen_tumbler Build the fullscreen-tumbler example. + * geturl Build the geturl example. + * hello_world Build the hello_world example. + * hello_world_c Build the hello_world_c example. + * input_events Build the input_events example. + * load_progress Build the load_progress example. + * mouselock Build the mouselock example. + * multithreaded_input_events Build the multithreaded input_events example. + * pi_generator Build the pi_generator example. + * pong Build the pong example. + * sine_synth Build the sine_synth example. + * tumbler Build the tumbler example. +""" + +example_directories = [ + 'fullscreen_tumbler', + 'geturl', + 'hello_world', + 'hello_world_c', + 'input_events', + 'load_progress', + 'mouselock', + 'multithreaded_input_events', + 'pi_generator', + 'pong', + 'sine_synth', + 'tumbler', + ] + +Help(HELP_STRING) + +staging_dir = os.path.abspath(os.getenv( + 'NACL_INSTALL_ROOT', os.path.join(os.getenv('NACL_SDK_ROOT', '.'), + 'staging'))) +general_files = Install(staging_dir, ['httpd.py']) +general_files.extend(InstallAs(os.path.join(staging_dir, 'index.html'), + 'index_staging.html')) + +if sys.platform in ['win32', 'cygwin']: + general_files.extend(Install(staging_dir, 'httpd.cmd')) + +SConscript([os.path.join(dir, 'build.scons') for dir in example_directories]) + +Default(['install'] + general_files + example_directories) +if GetOption('clean'): + print "Removing the staging directory at %s" % staging_dir + shutil.rmtree(staging_dir, ignore_errors=True) diff --git a/native_client_sdk/src/examples/common/check_browser.js b/native_client_sdk/src/examples/common/check_browser.js new file mode 100644 index 0000000..9636ad0 --- /dev/null +++ b/native_client_sdk/src/examples/common/check_browser.js @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2011 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * @fileoverview This file provides a BrowserChecker Javascript class. + * Users can create a BrowserChecker object, invoke checkBrowser(|version|), + * and then use getIsValidBrowser() and getBrowserSupportStatus() + * to determine if the browser version is greater than |version| + * and if the Native Client plugin is found. + */ + +// Create a namespace object +var browser_version = browser_version || {}; + +/** + * Class to provide checking for version and NativeClient. + * @param {integer} arg1 An argument that indicates major version of Chrome we + * require, such as 14. + */ + +/** + * Constructor for the BrowserChecker. Sets the major version of + * Chrome that is required to |minChromeVersion|. + * @param minChromeVersion The earliest major version of chrome that + * is supported. If the Chrome browser version is less than + * |minChromeVersion| then |isValidBrowswer| will be set to false. + * @param opt_maxChromeVersion Ignored. Retained for backwards compatibility. + * @param appVersion The application version string. + * @param plugins The plugins that exist in the browser. + * @constructor + */ +browser_version.BrowserChecker = function(minChromeVersion, + appVersion, plugins, + opt_maxChromeVersion) { + /** + * Version specified by the user. This class looks to see if the browser + * version is >= |minChromeVersion_|. + * @type {integer} + * @private + */ + this.minChromeVersion_ = minChromeVersion; + + /** + * List of Browser plugin objects. + * @type {Ojbect array} + * @private + */ + this.plugins_ = plugins; + + /** + * Application version string from the Browser. + * @type {integer} + * @private + */ + this.appVersion_ = appVersion; + + /** + * Flag used to indicate if the browser has Native Client and is if the + * browser version is recent enough. + * @type {boolean} + * @private + */ + this.isValidBrowser_ = false; + + /** + * Actual major version of Chrome -- found by querying the browser. + * @type {integer} + * @private + */ + this.chromeVersion_ = null; + + /** + * Browser support status. This allows the user to get a detailed status + * rather than using this.browserSupportMessage. + */ + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.UNKNOWN; +} + +/** + * The values used for BrowserChecker status to indicate success or + * a specific error. + * @enum {id} + */ +browser_version.BrowserChecker.StatusValues = { + UNKNOWN: 0, + NACL_ENABLED: 1, + UNKNOWN_BROWSER: 2, + CHROME_VERSION_TOO_OLD: 3, + NACL_NOT_ENABLED: 4, + NOT_USING_SERVER: 5 +}; + +/** + * Determines if the plugin with name |name| exists in the browser. + * @param {string} name The name of the plugin. + * @param {Object array} plugins The plugins in this browser. + * @return {bool} |true| if the plugin is found. + */ +browser_version.BrowserChecker.prototype.pluginExists = function(name, + plugins) { + for (var index=0; index < plugins.length; index++) { + var plugin = this.plugins_[index]; + var plugin_name = plugin['name']; + // If the plugin is not found, you can use the Javascript console + // to see the names of the plugins that were found when debugging. + if (plugin_name.indexOf(name) != -1) { + return true; + } + } + return false; +} + +/** + * Returns browserSupportStatus_ which indicates if the browser supports + * Native Client. Values are defined as literals in + * browser_version.BrowserChecker.StatusValues. + * @ return {int} Level of NaCl support. + */ +browser_version.BrowserChecker.prototype.getBrowserSupportStatus = function() { + return this.browserSupportStatus_; +} + +/** + * Returns isValidBrowser (true/false) to indicate if the browser supports + * Native Client. + * @ return {bool} If this browser has NativeClient and correct version. + */ +browser_version.BrowserChecker.prototype.getIsValidBrowser = function() { + return this.isValidBrowser_; +} + +/** + * Checks to see if this browser can support Native Client applications. + * For Chrome browsers, checks to see if the "Native Client" plugin is + * enabled. + */ +browser_version.BrowserChecker.prototype.checkBrowser = function() { + var versionPatt = /Chrome\/(\d+)\.(\d+)\.(\d+)\.(\d+)/; + var result = this.appVersion_.match(versionPatt); + + // |result| stores the Chrome version number. + if (!result) { + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER; + } else { + this.chromeVersion_ = result[1]; + // We know we have Chrome, check version and/or plugin named Native Client + if (this.chromeVersion_ >= this.minChromeVersion_) { + var found_nacl = this.pluginExists('Native Client', this.plugins_); + if (found_nacl) { + this.isValidBrowser_ = true; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.NACL_ENABLED; + } else { + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED; + } + } else { + // We are in a version that is less than |minChromeVersion_| + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD; + } + } + var my_protocol = window.location.protocol; + if (my_protocol.indexOf('file') == 0) { + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER; + } +} + diff --git a/native_client_sdk/src/examples/favicon.ico b/native_client_sdk/src/examples/favicon.ico Binary files differnew file mode 100644 index 0000000..ee7c943 --- /dev/null +++ b/native_client_sdk/src/examples/favicon.ico diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/bind.js b/native_client_sdk/src/examples/fullscreen_tumbler/bind.js new file mode 100644 index 0000000..92fbbd2 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/bind.js @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements an extension to Function object that + * lets you bind a scope for |this| to a function. + */ + +/** + * Bind a scope to a function. Used to bind an object to |this| for event + * handlers. + * @param {!Object} scope The scope in which the function executes. |scope| + * becomes |this| during function execution. + * @return {function} the bound version of the original function. + */ +Function.prototype.bind = function(scope) { + var boundContext = this; + return function() { + return boundContext.apply(scope, arguments); + } +} diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/build.scons b/native_client_sdk/src/examples/fullscreen_tumbler/build.scons new file mode 100644 index 0000000..26d14f2 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/build.scons @@ -0,0 +1,66 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='fullscreen_tumbler', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + LIBS=['ppapi_gles2'], + ) + +sources = [ + 'cube.cc', + 'opengl_context.cc', + 'scripting_bridge.cc', + 'shader_util.cc', + 'transforms.cc', + 'tumbler.cc', + 'tumbler_module.cc', + ] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'fullscreen_tumbler') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('fullscreen_tumbler') + +common_files = [ + 'check_browser.js', + ] +common_files = [ + os.path.join(os.path.dirname(os.getcwd()), 'common', common_file) + for common_file in common_files] + +app_files = [ + 'fullscreen_tumbler.html', + 'fullscreen_tumbler.nmf', + 'bind.js', + 'dragger.js', + 'trackball.js', + 'tumbler.js', + 'vector3.js', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']), + 'common') +install_common = nacl_env.Install(dir=common_dir, source=common_files) +nacl_env.Alias('install', + source=[install_app, install_common, install_nexes]) diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/callback.h b/native_client_sdk/src/examples/fullscreen_tumbler/callback.h new file mode 100644 index 0000000..4d67262 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/callback.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CALLBACK_H_ +#define EXAMPLES_TUMBLER_CALLBACK_H_ + +#include <map> +#include <string> +#include <vector> + +namespace tumbler { + +class ScriptingBridge; + +// Templates used to support method call-backs when a method or property is +// accessed from the browser code. + +// Class suite used to publish a method name to Javascript. Typical use is +// like this: +// photo::MethodCallback<Calculator>* calculate_callback_; +// calculate_callback_ = +// new scripting::MethodCallback<Calculator>(this, +// &Calculator::Calculate); +// bridge->AddMethodNamed("calculate", calculate_callback_); +// ... +// delete calculate_callback_; +// +// The caller must delete the callback. + +// Methods get parameters as a dictionary that maps parameter names to values. +typedef std::map<std::string, std::string> MethodParameter; + +// Pure virtual class used in STL containers. +class MethodCallbackExecutor { + public: + virtual ~MethodCallbackExecutor() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) = 0; +}; + +template <class T> +class MethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*Method)( + const ScriptingBridge& bridge, + const MethodParameter& parameters); + + MethodCallback(T* instance, Method method) + : instance_(instance), method_(method) {} + virtual ~MethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->method_))(bridge, parameters); + } + + private: + T* instance_; + Method method_; +}; + +template <class T> +class ConstMethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*ConstMethod)( + const ScriptingBridge& bridge, + const MethodParameter& parameters) const; + + ConstMethodCallback(const T* instance, ConstMethod method) + : instance_(instance), const_method_(method) {} + virtual ~ConstMethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->const_method_))(bridge, parameters); + } + + private: + const T* instance_; + ConstMethod const_method_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CALLBACK_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc b/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc new file mode 100644 index 0000000..5b8bc4c --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/cube.h" + +#include <algorithm> + +#include "examples/fullscreen_tumbler/shader_util.h" +#include "examples/fullscreen_tumbler/transforms.h" + +namespace tumbler { + +static const size_t kVertexCount = 24; +static const int kIndexCount = 36; + +Cube::Cube(SharedOpenGLContext opengl_context) + : opengl_context_(opengl_context), + width_(1), + height_(1) { + eye_[0] = eye_[1] = 0.0f; + eye_[2] = 2.0f; + orientation_[0] = 0.0f; + orientation_[1] = 0.0f; + orientation_[2] = 0.0f; + orientation_[3] = 1.0f; +} + +Cube::~Cube() { + glDeleteBuffers(3, cube_vbos_); + glDeleteProgram(shader_program_object_); +} + +void Cube::PrepareOpenGL() { + CreateShaders(); + CreateCube(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glEnable(GL_DEPTH_TEST); +} + +void Cube::Resize(int width, int height) { + width_ = std::max(width, 1); + height_ = std::max(height, 1); + // Set the viewport + glViewport(0, 0, width_, height_); + // Compute the perspective projection matrix with a 60 degree FOV. + GLfloat aspect = static_cast<GLfloat>(width_) / static_cast<GLfloat>(height_); + transform_4x4::LoadIdentity(perspective_proj_); + transform_4x4::Perspective(perspective_proj_, 60.0f, aspect, 1.0f, 20.0f); +} + +void Cube::Draw() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Compute a new model-view matrix, then use that to make the composite + // model-view-projection matrix: MVP = MV . P. + GLfloat model_view[16]; + ComputeModelViewTransform(model_view); + transform_4x4::Multiply(mvp_matrix_, model_view, perspective_proj_); + + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glUseProgram(shader_program_object_); + glEnableVertexAttribArray(position_location_); + glVertexAttribPointer(position_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glEnableVertexAttribArray(color_location_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glVertexAttribPointer(color_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glUniformMatrix4fv(mvp_location_, 1, GL_FALSE, mvp_matrix_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glDrawElements(GL_TRIANGLES, kIndexCount, GL_UNSIGNED_SHORT, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +bool Cube::CreateShaders() { + const char vertex_shader_src[] = + "uniform mat4 u_mvpMatrix; \n" + "attribute vec4 a_position; \n" + "attribute vec3 a_color; \n" + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " v_color.xyz = a_color; \n" + " v_color.w = 1.0; \n" + " gl_Position = u_mvpMatrix * a_position; \n" + "} \n"; + + const char fragment_shader_src[] = + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " gl_FragColor = v_color; \n" + "} \n"; + + // Load the shaders and get a linked program object + shader_program_object_ = + shader_util::CreateProgramFromVertexAndFragmentShaders( + vertex_shader_src, fragment_shader_src); + if (shader_program_object_ == 0) + return false; + position_location_ = glGetAttribLocation(shader_program_object_, + "a_position"); + color_location_ = glGetAttribLocation(shader_program_object_, "a_color"); + mvp_location_ = glGetUniformLocation(shader_program_object_, "u_mvpMatrix"); + return true; +} + +void Cube::CreateCube() { + static const GLfloat cube_vertices[] = { + // Vertex coordinates interleaved with color values + // Bottom + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, -0.5f, + // Top + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + // Back + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + // Front + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + // Left + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + // Right + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f + }; + + static const GLfloat cube_colors[] = { + // Vertex coordinates interleaved with color values + // Bottom + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + // Back + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + // Front + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + // Left + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + // Right + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0 + }; + + static const GLushort cube_indices[] = { + // Bottom + 0, 2, 1, + 0, 3, 2, + // Top + 4, 5, 6, + 4, 6, 7, + // Back + 8, 9, 10, + 8, 10, 11, + // Front + 12, 15, 14, + 12, 14, 13, + // Left + 16, 17, 18, + 16, 18, 19, + // Right + 20, 23, 22, + 20, 22, 21 + }; + + // Generate the VBOs and upload them to the graphics context. + glGenBuffers(3, cube_vbos_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_vertices, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_colors, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + kIndexCount * sizeof(GL_UNSIGNED_SHORT), + cube_indices, + GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +void Cube::ComputeModelViewTransform(GLfloat* model_view) { + // This method takes into account the possiblity that |orientation_| + // might not be normalized. + double sqrx = orientation_[0] * orientation_[0]; + double sqry = orientation_[1] * orientation_[1]; + double sqrz = orientation_[2] * orientation_[2]; + double sqrw = orientation_[3] * orientation_[3]; + double sqrLength = 1.0 / (sqrx + sqry + sqrz + sqrw); + + transform_4x4::LoadIdentity(model_view); + model_view[0] = (sqrx - sqry - sqrz + sqrw) * sqrLength; + model_view[5] = (-sqrx + sqry - sqrz + sqrw) * sqrLength; + model_view[10] = (-sqrx - sqry + sqrz + sqrw) * sqrLength; + + double temp1 = orientation_[0] * orientation_[1]; + double temp2 = orientation_[2] * orientation_[3]; + model_view[1] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[4] = 2.0 * (temp1 - temp2) * sqrLength; + + temp1 = orientation_[0] * orientation_[2]; + temp2 = orientation_[1] * orientation_[3]; + model_view[2] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[8] = 2.0 * (temp1 + temp2) * sqrLength; + temp1 = orientation_[1] * orientation_[2]; + temp2 = orientation_[0] * orientation_[3]; + model_view[6] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[9] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[3] = 0.0; + model_view[7] = 0.0; + model_view[11] = 0.0; + + // Concatenate the translation to the eye point. + model_view[12] = -eye_[0]; + model_view[13] = -eye_[1]; + model_view[14] = -eye_[2]; + model_view[15] = 1.0; +} + +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/cube.h b/native_client_sdk/src/examples/fullscreen_tumbler/cube.h new file mode 100644 index 0000000..42af1cc --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/cube.h @@ -0,0 +1,98 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CUBE_H_ +#define EXAMPLES_TUMBLER_CUBE_H_ + +#include <GLES2/gl2.h> +#include <vector> +#include "examples/fullscreen_tumbler/opengl_context.h" +#include "examples/fullscreen_tumbler/opengl_context_ptrs.h" + +namespace tumbler { + +// The Cube class provides a place to implement 3D rendering. It has a +// frame that it occupies in a browser window. +class Cube { + public: + explicit Cube(SharedOpenGLContext opengl_context); + ~Cube(); + + // Called once when a new RenderContext is first bound to the view. The + // bound context is guaranteed to be current and valid before calling this + // method. + void PrepareOpenGL(); + + // Called whenever the size of the browser view changes. This method is + // called at least once when the view is first made visible. Clamps the + // sizes to 1. + void Resize(int width, int height); + + // Called every time the view need to be drawn. The bound context is + // guaranteed to be current and valid before this method is called. The + // visible portion of the context is flushed to the browser after this + // method returns. + void Draw(); + + // Accessor for width and height. To change these, call Resize. + const int width() const { + return width_; + } + + const int height() const { + return height_; + } + + // Accessor/mutator for the camera orientation. + void GetOrientation(std::vector<float>* orientation) const { + if (!orientation) + return; + (*orientation)[0] = static_cast<float>(orientation_[0]); + (*orientation)[1] = static_cast<float>(orientation_[1]); + (*orientation)[2] = static_cast<float>(orientation_[2]); + (*orientation)[3] = static_cast<float>(orientation_[3]); + } + void SetOrientation(const std::vector<float>& orientation) { + orientation_[0] = static_cast<GLfloat>(orientation[0]); + orientation_[1] = static_cast<GLfloat>(orientation[1]); + orientation_[2] = static_cast<GLfloat>(orientation[2]); + orientation_[3] = static_cast<GLfloat>(orientation[3]); + } + + private: + // Create the shaders used to draw the cube, and link them into a program. + // Initializes |shader_progam_object_|, |position_loction_| and + // |mvp_location_|. + bool CreateShaders(); + + // Generates a cube as a series of GL_TRIANGLE_STRIPs, and initializes + // |index_count_| to the number of indices in the index list used as a VBO. + // Creates the |vbo_ids_| required for the vertex and index data and uploads + // the the VBO data. + void CreateCube(); + + // Build up the model-view transform from the eye and orienation properties. + // Assumes that |model_view| is a 4x4 matrix. + void ComputeModelViewTransform(GLfloat* model_view); + + SharedOpenGLContext opengl_context_; + int width_; + int height_; + GLuint shader_program_object_; // The compiled shaders. + GLint position_location_; // The position attribute location. + GLint color_location_; // The color attribute location. + GLint mvp_location_; // The Model-View-Projection composite matrix. + GLuint cube_vbos_[3]; + GLfloat eye_[3]; // The eye point of the virtual camera. + // The orientation of the virtual camera stored as a quaternion. The + // quaternion is laid out as {{x, y, z}, w}. + GLfloat orientation_[4]; + GLfloat perspective_proj_[16]; + GLfloat mvp_matrix_[16]; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CUBE_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js b/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js new file mode 100644 index 0000000..232d8b5 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js @@ -0,0 +1,134 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements a mouse-drag event. It registers for + * mousedown events, and when it sees one, starts capturing mousemove events + * until it gets a mousup event. It manufactures three drag events: the + * DRAG_START, DRAG and DRAG_END. + */ + +// Requires bind + +/** + * Constructor for the Dragger. Register for mousedown events that happen on + * |opt_target|. If |opt_target| is null or undefined, then this object + * observes mousedown on the whole document. + * @param {?Element} opt_target The event target. Defaults to the whole + * document. + * @constructor + */ +tumbler.Dragger = function(opt_target) { + /** + * The event target. + * @type {Element} + * @private + */ + this.target_ = opt_target || document; + + /** + * The array of objects that get notified of drag events. Each object in + * this array get sent a handleStartDrag(), handleDrag() and handleEndDrag() + * message. + * @type {Array.<Object>} + * @private + */ + this.listeners_ = []; + + /** + * Flag to indicate whether the object is in a drag sequence or not. + * @type {boolean} + * @private + */ + this.isDragging_ = false; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed on mouse up. + * @type {function} + * @private + */ + this.boundMouseMove_ = null; + this.boundMouseUp_ = null; + + this.target_.addEventListener('mousedown', + this.onMouseDown.bind(this), + false); +} + +/** + * The ids used for drag event types. + * @enum {string} + */ +tumbler.Dragger.DragEvents = { + DRAG_START: 'dragstart', // Start a drag sequence + DRAG: 'drag', // Mouse moved during a drag sequence. + DRAG_END: 'dragend' // End a drag sewquence. +}; + +/** + * Add a drag listener. Each listener should respond to thhree methods: + * handleStartDrag(), handleDrag() and handleEndDrag(). This method assumes + * that |listener| does not already exist in the array of listeners. + * @param {!Object} listener The object that will listen to drag events. + */ +tumbler.Dragger.prototype.addDragListener = function(listener) { + this.listeners_.push(listener); +} + +/** + * Handle a mousedown event: register for mousemove and mouseup, then tell + * the target that is has a DRAG_START event. + * @param {Event} event The mousedown event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseDown = function(event) { + this.boundMouseMove_ = this.onMouseMove.bind(this); + this.boundMouseUp_ = this.onMouseUp.bind(this); + this.target_.addEventListener('mousemove', this.boundMouseMove_); + this.target_.addEventListener('mouseup', this.boundMouseUp_); + this.isDragging_ = true; + var dragStartEvent = { type: tumbler.Dragger.DragEvents.DRAG_START, + clientX: event.offsetX, + clientY: event.offsetY }; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleStartDrag(this.target_, dragStartEvent); + } +} + +/** + * Handle a mousemove event: tell the target that is has a DRAG event. + * @param {Event} event The mousemove event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseMove = function(event) { + if (!this.isDragging_) + return; + var dragEvent = { type: tumbler.Dragger.DragEvents.DRAG, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleDrag(this.target_, dragEvent); + } +} + +/** + * Handle a mouseup event: un-register for mousemove and mouseup, then tell + * the target that is has a DRAG_END event. + * @param {Event} event The mouseup event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseUp = function(event) { + this.target_.removeEventListener('mouseup', this.boundMouseUp_, false); + this.target_.removeEventListener('mousemove', this.boundMouseMove_, false); + this.boundMouseUp_ = null; + this.boundMouseMove_ = null; + this.isDragging_ = false; + var dragEndEvent = { type: tumbler.Dragger.DragEvents.DRAG_END, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleEndDrag(this.target_, dragEndEvent); + } +} diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html b/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html new file mode 100644 index 0000000..a255b66 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html @@ -0,0 +1,57 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Interactive Cube Fullscreen Example</title> + <script type="text/javascript"> + // Provide the tumbler namespace + tumbler = {}; + // Fullscreen support is in Chrome version 16. + tumbler.CHROME_MINIMUM_VERSION = 16; + </script> + <script type="text/javascript" src="../common/check_browser.js"></script> + <script type="text/javascript" src="bind.js"></script> + <script type="text/javascript" src="dragger.js"></script> + <script type="text/javascript" src="tumbler.js"></script> + <script type="text/javascript" src="vector3.js"></script> + <script type="text/javascript" src="trackball.js"></script> + <script type="text/javascript"> + // Check for Native Client support in the browser before the DOM loads. + var isValidBrowser = false; + var browserSupportStatus = 0; + var checker = new browser_version.BrowserChecker( + tumbler.CHROME_MINIMUM_VERSION, + navigator["appVersion"], + navigator["plugins"]); + checker.checkBrowser(); + + isValidBrowser = checker.getIsValidBrowser(); + browserSupportStatus = checker.getBrowserSupportStatus(); + </script> + </head> + <body id="bodyId"> + <h1>Interactive Cube Example</h1> + <p> + The Native Client module executed in this page draws a 3D cube + and allows you to rotate it using a virtual trackball method. To toggle + the view to/from fullscreen, press the Enter key. + </p> + <div id="tumbler_view"></div> + <script type="text/javascript"> + if (browserSupportStatus == + browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD) { + alert('This example will only work on Chrome version ' + + tumbler.CHROME_MINIMUM_VERSION + + ' or later.'); + } else { + tumbler.application = new tumbler.Application(); + tumbler.application.run('tumbler_view'); + } + </script> + </body> +</HTML> diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc new file mode 100644 index 0000000..98ee3c3 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/opengl_context.h" + +#include <pthread.h> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +namespace { +// This is called by the brower when the 3D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<tumbler::OpenGLContext*>(data)->set_flush_pending(false); +} +} // namespace + +namespace tumbler { + +OpenGLContext::OpenGLContext(pp::Instance* instance) + : pp::Graphics3DClient(instance), + flush_pending_(false) { + pp::Module* module = pp::Module::Get(); + assert(module); + gles2_interface_ = static_cast<const struct PPB_OpenGLES2*>( + module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE)); + assert(gles2_interface_); +} + +OpenGLContext::~OpenGLContext() { + glSetCurrentContextPPAPI(0); +} + +bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) { + if (instance == NULL) { + glSetCurrentContextPPAPI(0); + return false; + } + // Lazily create the Pepper context. + if (context_.is_null()) { + int32_t attribs[] = { + PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, + PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24, + PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8, + PP_GRAPHICS3DATTRIB_SAMPLES, 0, + PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0, + PP_GRAPHICS3DATTRIB_WIDTH, size_.width(), + PP_GRAPHICS3DATTRIB_HEIGHT, size_.height(), + PP_GRAPHICS3DATTRIB_NONE + }; + context_ = pp::Graphics3D(instance, pp::Graphics3D(), attribs); + if (context_.is_null()) { + glSetCurrentContextPPAPI(0); + return false; + } + instance->BindGraphics(context_); + } + glSetCurrentContextPPAPI(context_.pp_resource()); + return true; +} + +void OpenGLContext::InvalidateContext(pp::Instance* instance) { + glSetCurrentContextPPAPI(0); +} + +void OpenGLContext::ResizeContext(const pp::Size& size) { + size_ = size; + if (!context_.is_null()) { + context_.ResizeBuffers(size.width(), size.height()); + } +} + + +void OpenGLContext::FlushContext() { + if (flush_pending()) { + // A flush is pending so do nothing; just drop this flush on the floor. + return; + } + set_flush_pending(true); + context_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this)); +} +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h new file mode 100644 index 0000000..6a5369b --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h @@ -0,0 +1,94 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + +/// +/// @file +/// OpenGLContext manages the OpenGL context in the browser that is associated +/// with a @a pp::Instance instance. +/// + +#include <assert.h> +#include <pthread.h> + +#include <algorithm> +#include <string> + +#include "examples/fullscreen_tumbler/opengl_context_ptrs.h" +#include "ppapi/c/ppb_opengles2.h" +#include "ppapi/cpp/graphics_3d.h" +#include "ppapi/cpp/graphics_3d_client.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/size.h" + +namespace tumbler { + +/// OpenGLContext manages an OpenGL rendering context in the browser. +/// +class OpenGLContext : public pp::Graphics3DClient { + public: + explicit OpenGLContext(pp::Instance* instance); + + /// Release all the in-browser resources used by this context, and make this + /// context invalid. + virtual ~OpenGLContext(); + + /// The Graphics3DClient interfcace. + virtual void Graphics3DContextLost() { + assert(!"Unexpectedly lost graphics context"); + } + + /// Make @a this the current 3D context in @a instance. + /// @param instance The instance of the NaCl module that will receive the + /// the current 3D context. + /// @return success. + bool MakeContextCurrent(pp::Instance* instance); + + /// Flush the contents of this context to the browser's 3D device. + void FlushContext(); + + /// Make the underlying 3D device invalid, so that any subsequent rendering + /// commands will have no effect. The next call to MakeContextCurrent() will + /// cause the underlying 3D device to get rebound and start receiving + /// receiving rendering commands again. Use InvalidateContext(), for + /// example, when resizing the context's viewing area. + void InvalidateContext(pp::Instance* instance); + + /// Resize the context. + void ResizeContext(const pp::Size& size); + + /// The OpenGL ES 2.0 interface. + const struct PPB_OpenGLES2* gles2() const { + return gles2_interface_; + } + + /// The PP_Resource needed to make GLES2 calls through the Pepper interface. + const PP_Resource gl_context() const { + return context_.pp_resource(); + } + + /// Indicate whether a flush is pending. This can only be called from the + /// main thread; it is not thread safe. + bool flush_pending() const { + return flush_pending_; + } + void set_flush_pending(bool flag) { + flush_pending_ = flag; + } + + private: + pp::Size size_; + pp::Graphics3D context_; + bool flush_pending_; + + const struct PPB_OpenGLES2* gles2_interface_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h new file mode 100644 index 0000000..3478521 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + +// A convenience wrapper for a shared OpenGLContext pointer type. As other +// smart pointer types are needed, add them here. + +#include <tr1/memory> + +namespace tumbler { + +class OpenGLContext; + +typedef std::tr1::shared_ptr<OpenGLContext> SharedOpenGLContext; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc new file mode 100644 index 0000000..8a7a54d --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/scripting_bridge.h" + +namespace { +const char* const kWhiteSpaceCharacters = " \t"; + +// Helper function to pull out the next token in |token_string|. A token is +// delimited by whitespace. Scanning begins at |*pos|, if pos goes beyond the +// end of |token_string|, it is set to std::string::npos and an empty string +// is returned. On return, |*pos| will point to the beginning of the next +// token. |pos| must not be NULL. +const std::string ScanToken(const std::string& token_string, size_t* pos) { + std::string token; + if (*pos == std::string::npos) { + return token; + } + size_t token_start_pos = token_string.find_first_not_of(kWhiteSpaceCharacters, + *pos); + size_t token_end_pos = token_string.find_first_of(kWhiteSpaceCharacters, + token_start_pos); + if (token_start_pos != std::string::npos) { + token = token_string.substr(token_start_pos, token_end_pos); + } + *pos = token_end_pos; + return token; +} + +// Take a string of the form 'name:value' and split it into two strings, one +// containing 'name' and the other 'value'. If the ':' separator is missing, +// or is the last character in |parameter|, |parameter| is copied to +// |param_name|, |param_value| is left unchanged and false is returned. +bool ParseParameter(const std::string& parameter, + std::string* param_name, + std::string* param_value) { + bool success = false; + size_t sep_pos = parameter.find_first_of(':'); + if (sep_pos != std::string::npos) { + *param_name = parameter.substr(0, sep_pos); + if (sep_pos < parameter.length() - 1) { + *param_value = parameter.substr(sep_pos + 1); + success = true; + } else { + success = false; + } + } else { + *param_name = parameter; + success = false; + } + return success; +} +} // namespace + +namespace tumbler { + +bool ScriptingBridge::AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method) { + if (method_name.size() == 0 || method == NULL) + return false; + method_dictionary_.insert( + std::pair<std::string, SharedMethodCallbackExecutor>(method_name, + method)); + return true; +} + +bool ScriptingBridge::InvokeMethod(const std::string& method) { + size_t current_pos = 0; + const std::string method_name = ScanToken(method, ¤t_pos); + MethodDictionary::iterator method_iter; + method_iter = method_dictionary_.find(method_name); + if (method_iter != method_dictionary_.end()) { + // Pull out the method parameters and build a dictionary that maps + // parameter names to values. + std::map<std::string, std::string> param_dict; + while (current_pos != std::string::npos) { + const std::string parameter = ScanToken(method, ¤t_pos); + if (parameter.length()) { + std::string param_name; + std::string param_value; + if (ParseParameter(parameter, ¶m_name, ¶m_value)) { + // Note that duplicate parameter names will override each other. The + // last one in the method string will be used. + param_dict[param_name] = param_value; + } + } + } + (*method_iter->second).Execute(*this, param_dict); + return true; + } + return false; +} + +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h new file mode 100644 index 0000000..b1888cf --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ +#define EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ + +#include <map> +#include <string> +#include <tr1/memory> +#include <vector> + +#include "examples/fullscreen_tumbler/callback.h" +#include "ppapi/cpp/var.h" + +namespace tumbler { + +class MethodCallbackExecutor; + +// This class handles the interface between the browser and the NaCl module. +// There is a single point of entry from the browser: postMessage(). The +// string passed to postMessage() has this format: +// 'function_name arg_name0:arg_0 arg_name1:arg1 ...' +// The arguments have undetermined type; they are placed in a map of argument +// names and values. Values are all strings, it is up to the target code to +// do any type coercion. +// Methods called by the scripting bridge must have a signature like this: +// void Method(const ScriptingBridge& bridge, +// const ParameterDictionary&); +class ScriptingBridge { + public: + // Shared pointer type used in the method map. + typedef std::tr1::shared_ptr<MethodCallbackExecutor> + SharedMethodCallbackExecutor; + + virtual ~ScriptingBridge() {} + + // Causes |method_name| to be published as a method that can be called via + // postMessage() from the browser. Associates this method with |method|. + bool AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method); + + bool InvokeMethod(const std::string& method); + + private: + typedef std::map<std::string, SharedMethodCallbackExecutor> MethodDictionary; + + MethodDictionary method_dictionary_; +}; + +} // namespace tumbler +#endif // EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc new file mode 100644 index 0000000..c2c647d --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/shader_util.h" + +#include <stdio.h> +#include <stdlib.h> + +namespace shader_util { + +GLuint CreateShaderOfType(GLenum type, const char *shader_src) { + GLuint shader; + GLint compiled; + + // Create the shader object + shader = glCreateShader(type); + + if (shader == 0) + return 0; + + // Load and compile the shader source + glShaderSource(shader, 1, &shader_src, NULL); + glCompileShader(shader); + + // Check the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled == 0) { + GLint info_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = + reinterpret_cast<char*>(malloc(sizeof(*info_log) * info_len)); + glGetShaderInfoLog(shader, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error compiling shader:\n%s\n", info_log); + free(info_log); + } + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src) { + GLuint vertex_shader; + GLuint fragment_shader; + GLuint program_object; + GLint linked; + + // Load the vertex/fragment shaders + vertex_shader = CreateShaderOfType(GL_VERTEX_SHADER, vertex_shader_src); + if (vertex_shader == 0) + return 0; + fragment_shader = CreateShaderOfType(GL_FRAGMENT_SHADER, fragment_shader_src); + if (fragment_shader == 0) { + glDeleteShader(vertex_shader); + return 0; + } + + // Create the program object and attach the shaders. + program_object = glCreateProgram(); + if (program_object == 0) + return 0; + glAttachShader(program_object, vertex_shader); + glAttachShader(program_object, fragment_shader); + + // Link the program + glLinkProgram(program_object); + + // Check the link status + glGetProgramiv(program_object, GL_LINK_STATUS, &linked); + if (linked == 0) { + GLint info_len = 0; + glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast<char*>(malloc(info_len)); + glGetProgramInfoLog(program_object, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error linking program:\n%s\n", info_log); + free(info_log); + } + glDeleteProgram(program_object); + return 0; + } + + // Delete these here because they are attached to the program object. + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program_object; +} + +} // namespace shader_util + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h new file mode 100644 index 0000000..fdf9cbd --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h @@ -0,0 +1,30 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Some simple helper functions that load shaders and create program objects. + +#ifndef EXAMPLES_TUMBLER_SHADER_UTIL_H_ +#define EXAMPLES_TUMBLER_SHADER_UTIL_H_ + +#include <GLES2/gl2.h> + +namespace shader_util { + +// Load and compile a shader. |type| can be one of GL_VERTEX_SHADER or +// GL_FRAGMENT_SHADER. Returns a non-0 value representing the compiled +// shader on success, 0 on failure. The caller is responsible for deleting +// the returned shader using glDeleteShader(). +GLuint CreateShaderOfType(GLenum type, const char *shader_src); + +// Load and compile the vertex and fragment shaders, then link these together +// into a complete program. Returns a non-0 value representing the program on, +// success or 0 on failure. The caller is responsible for deleting the +// returned program using glDeleteProgram(). +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src); + +} // namespace shader_util + +#endif // EXAMPLES_TUMBLER_SHADER_UTIL_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js b/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js new file mode 100644 index 0000000..88b9a62 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js @@ -0,0 +1,296 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Implement a virtual trackball in the tumbler.Trackball + * class. This class maps 2D mouse events to 3D rotations by simulating a + * trackball that you roll by dragging the mouse. There are two principle + * methods in the class: startAtPointInFrame which you use to begin a trackball + * simulation and rollToPoint, which you use while dragging the mouse. The + * rollToPoint method returns a rotation expressed as a quaternion. + */ + + +// Requires tumbler.Application +// Requires tumbler.DragEvent +// Requires tumbler.Vector3 + +/** + * Constructor for the Trackball object. This class maps 2D mouse drag events + * into 3D rotations by simulating a trackball. The idea is to simulate + * clicking on the trackball, and then rolling it as you drag the mouse. + * The math behind the trackball is simple: start with a vector from the first + * mouse-click on the ball to the center of the 3D view. At the same time, set + * the radius of the ball to be the smaller dimension of the 3D view. As you + * drag the mouse around in the 3D view, a second vector is computed from the + * surface of the ball to the center. The axis of rotation is the cross + * product of these two vectors, and the angle of rotation is the angle between + * the two vectors. + * @constructor + */ +tumbler.Trackball = function() { + /** + * The square of the trackball's radius. The math never looks at the radius, + * but looks at the radius squared. + * @type {number} + * @private + */ + this.sqrRadius_ = 0; + + /** + * The 3D vector representing the point on the trackball where the mouse + * was clicked. Default is pointing stright through the center of the ball. + * @type {Object} + * @private + */ + this.rollStart_ = new tumbler.Vector3(0, 0, 1); + + /** + * The 2D center of the frame that encloses the trackball. + * @type {!Object} + * @private + */ + this.center_ = { x: 0, y: 0 }; + + /** + * Cached camera orientation. When a drag START event happens this is set to + * the current orientation in the calling view's plugin. The default is the + * identity quaternion. + * @type {Array.<number>} + * @private + */ + this.cameraOrientation_ = [0, 0, 0, 1]; +}; + +/** + * Compute the dimensions of the virtual trackball to fit inside |frameSize|. + * The radius of the trackball is set to be 1/2 of the smaller of the two frame + * dimensions, the center point is at the midpoint of each side. + * @param {!goog.math.Size} frameSize 2D-point representing the size of the + * element that encloses the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.initInFrame_ = function(frameSize) { + // Compute the radius of the virtual trackball. This is 1/2 of the smaller + // of the frame's width and height. + var halfFrameSize = 0.5 * Math.min(frameSize.width, frameSize.height); + // Cache the square of the trackball's radius. + this.sqrRadius_ = halfFrameSize * halfFrameSize; + // Figure the center of the view. + this.center_.x = frameSize.width * 0.5; + this.center_.y = frameSize.height * 0.5; +}; + +/** + * Method to convert (by translation) a 2D client point from a coordinate space + * with origin in the lower-left corner of the client view to a space with + * origin in the center of the client view. Use this method before mapping the + * 2D point to he 3D tackball point (see also the projectOnTrackball_() method). + * Call the startAtPointInFrame before calling this method so that the + * |center_| property is correctly initialized. + * @param {!Object} clientPoint map this point to the coordinate space with + * origin in thecenter of the client view. + * @return {Object} the converted point. + * @private + */ +tumbler.Trackball.prototype.convertClientPoint_ = function(clientPoint) { + var difference = { x: clientPoint.x - this.center_.x, + y: clientPoint.y - this.center_.y } + return difference; +}; + +/** + * Method to map a 2D point to a 3D point on the virtual trackball that was set + * up using the startAtPointInFrame method. If the point lies outside of the + * radius of the virtual trackball, then the z-coordinate of the 3D point + * is set to 0. + * @param {!Object.<x, y>} point 2D-point in the coordinate space with origin + * in the center of the client view. + * @return {tumbler.Vector3} the 3D point on the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.projectOnTrackball_ = function(point) { + var sqrRadius2D = point.x * point.x + point.y * point.y; + var zValue; + if (sqrRadius2D > this.sqrRadius_) { + // |point| lies outside the virtual trackball's sphere, so use a virtual + // z-value of 0. This is equivalent to clicking on the horizontal equator + // of the trackball. + zValue = 0; + } else { + // A sphere can be defined as: r^2 = x^2 + y^2 + z^2, so z = + // sqrt(r^2 - (x^2 + y^2)). + zValue = Math.sqrt(this.sqrRadius_ - sqrRadius2D); + } + var trackballPoint = new tumbler.Vector3(point.x, point.y, zValue); + return trackballPoint; +}; + +/** + * Method to start up the trackball. The trackball works by pretending that a + * ball encloses the 3D view. You roll this pretend ball with the mouse. For + * example, if you click on the center of the ball and move the mouse straight + * to the right, you roll the ball around its Y-axis. This produces a Y-axis + * rotation. You can click on the "edge" of the ball and roll it around + * in a circle to get a Z-axis rotation. + * @param {!Object.<x, y>} startPoint 2D-point, usually the mouse-down + * point. + * @param {!Object.<width, height>} frameSize 2D-point representing the size of + * the element that encloses the virtual trackball. + */ +tumbler.Trackball.prototype.startAtPointInFrame = + function(startPoint, frameSize) { + this.initInFrame_(frameSize); + // Compute the starting vector from the surface of the ball to its center. + this.rollStart_ = this.projectOnTrackball_( + this.convertClientPoint_(startPoint)); +}; + +/** + * Method to roll the virtual trackball; call this in response to a mouseDrag + * event. Takes |dragPoint| and projects it from 2D mouse coordinates onto the + * virtual track ball that was set up in startAtPointInFrame method. + * Returns a quaternion that represents the rotation from |rollStart_| to + * |rollEnd_|. + * @param {!Object.<x, y>} dragPoint 2D-point representing the + * destination mouse point. + * @return {Array.<number>} a quaternion that represents the rotation from + * the point wnere the mouse was clicked on the trackball to this point. + * The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + * imaginary part of the quaternion and is computed as [x, y, z] * + * sin(angle/2). + */ +tumbler.Trackball.prototype.rollToPoint = function(dragPoint) { + var rollTo = this.convertClientPoint_(dragPoint); + if ((Math.abs(this.rollStart_.x - rollTo.x) < + tumbler.Trackball.DOUBLE_EPSILON) && + (Math.abs(this.rollStart_.y, rollTo.y) < + tumbler.Trackball.DOUBLE_EPSILON)) { + // Not enough change in the vectors to roll the ball, return the identity + // quaternion. + return [0, 0, 0, 1]; + } + + // Compute the ending vector from the surface of the ball to its center. + var rollEnd = this.projectOnTrackball_(rollTo); + + // Take the cross product of the two vectors. r = s X e + var rollVector = this.rollStart_.cross(rollEnd); + var invStartMag = 1.0 / this.rollStart_.magnitude(); + var invEndMag = 1.0 / rollEnd.magnitude(); + + // cos(a) = (s . e) / (||s|| ||e||) + var cosAng = this.rollStart_.dot(rollEnd) * invStartMag * invEndMag; + // sin(a) = ||(s X e)|| / (||s|| ||e||) + var sinAng = rollVector.magnitude() * invStartMag * invEndMag; + // Build a quaternion that represents the rotation about |rollVector|. + // Use atan2 for a better angle. If you use only cos or sin, you only get + // half the possible angles, and you can end up with rotations that flip + // around near the poles. + var rollHalfAngle = Math.atan2(sinAng, cosAng) * 0.5; + rollVector.normalize(); + // The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + // imaginary part of the quaternion and is computed as [x, y, z] * + // sin(angle/2). + rollVector.scale(Math.sin(rollHalfAngle)); + var ballQuaternion = [rollVector.x, + rollVector.y, + rollVector.z, + Math.cos(rollHalfAngle)]; + return ballQuaternion; +}; + +/** + * Handle the drag START event: grab the current camera orientation from the + * sending view and set up the virtual trackball. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragStartEvent The DRAG_START event that + * triggered this handler. + */ +tumbler.Trackball.prototype.handleStartDrag = + function(controller, dragStartEvent) { + // Cache the camera orientation. The orientations from the trackball as it + // rolls are concatenated to this orientation and pushed back into the + // plugin on the other side of the JavaScript bridge. + controller.setCameraOrientation(this.cameraOrientation_); + // Invert the y-coordinate for the trackball computations. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragStartEvent.clientX, + y: frameSize.height - dragStartEvent.clientY }; + this.startAtPointInFrame(flippedY, frameSize); +}; + +/** + * Handle the drag DRAG event: concatenate the current orientation to the + * cached orientation. Send this final value through to the GSPlugin via the + * setValueForKey() method. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEvent The DRAG event that triggered this + * handler. + */ +tumbler.Trackball.prototype.handleDrag = + function(controller, dragEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEvent.clientX, + y: frameSize.height - dragEvent.clientY }; + controller.setCameraOrientation( + tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_)); +}; + +/** + * Handle the drag END event: get the final orientation and concatenate it to + * the cached orientation. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEndEvent The DRAG_END event that triggered + * this handler. + */ +tumbler.Trackball.prototype.handleEndDrag = + function(controller, dragEndEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEndEvent.clientX, + y: frameSize.height - dragEndEvent.clientY }; + this.cameraOrientation_ = tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_); + controller.setCameraOrientation(this.cameraOrientation_); +}; + +/** + * A utility function to multiply two quaterions. Returns the product q0 * q1. + * This is effectively the same thing as concatenating the two rotations + * represented in each quaternion together. Note that quaternion multiplication + * is NOT commutative: q0 * q1 != q1 * q0. + * @param {!Array.<number>} q0 A 4-element array representing the first + * quaternion. + * @param {!Array.<number>} q1 A 4-element array representing the second + * quaternion. + * @return {Array.<number>} A 4-element array representing the product q0 * q1. + */ +tumbler.multQuaternions = function(q0, q1) { + // Return q0 * q1 (note the order). + var qMult = [ + q0[3] * q1[0] + q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1], + q0[3] * q1[1] - q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0], + q0[3] * q1[2] + q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3], + q0[3] * q1[3] - q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + ]; + return qMult; +}; + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in Closure somewhere (goog.math?). + * @type {number} + */ +tumbler.Trackball.DOUBLE_EPSILON = 1.0e-16; diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc new file mode 100644 index 0000000..609f8fd --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/transforms.h" + +#include <GLES2/gl2.h> +#include <math.h> +#include <string.h> + +namespace transform_4x4 { + +static const GLfloat kPI = 3.1415926535897932384626433832795f; + +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz) { + m[12] += (m[0] * tx + m[4] * ty + m[8] * tz); + m[13] += (m[1] * tx + m[5] * ty + m[9] * tz); + m[14] += (m[2] * tx + m[6] * ty + m[10] * tz); + m[15] += (m[3] * tx + m[7] * ty + m[11] * tz); +} + +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z) { + GLfloat delta_x = right - left; + GLfloat delta_y = top - bottom; + GLfloat delta_z = far_z - near_z; + GLfloat frustum[16]; + + if ((near_z <= 0.0f) || (far_z <= 0.0f) || + (delta_x <= 0.0f) || (delta_y <= 0.0f) || (delta_z <= 0.0f)) + return; + + frustum[0] = 2.0f * near_z / delta_x; + frustum[1] = frustum[2] = frustum[3] = 0.0f; + + frustum[5] = 2.0f * near_z / delta_y; + frustum[4] = frustum[6] = frustum[7] = 0.0f; + + frustum[8] = (right + left) / delta_x; + frustum[9] = (top + bottom) / delta_y; + frustum[10] = -(near_z + far_z) / delta_z; + frustum[11] = -1.0f; + + frustum[14] = -2.0f * near_z * far_z / delta_z; + frustum[12] = frustum[13] = frustum[15] = 0.0f; + + transform_4x4::Multiply(m, frustum, m); +} + + +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z) { + GLfloat frustum_w, frustum_h; + + frustum_h = tanf((fovy * 0.5f) / 180.0f * kPI) * near_z; + frustum_w = frustum_h * aspect; + transform_4x4::Frustum(m, -frustum_w, frustum_w, -frustum_h, frustum_h, + near_z, far_z); +} + +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b) { + GLfloat tmp[16]; + // tmp = a . b + GLfloat a0, a1, a2, a3; + a0 = a[0]; + a1 = a[1]; + a2 = a[2]; + a3 = a[3]; + tmp[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[4]; + a1 = a[5]; + a2 = a[6]; + a3 = a[7]; + tmp[4] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[5] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[6] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[7] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[8]; + a1 = a[9]; + a2 = a[10]; + a3 = a[11]; + tmp[8] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[9] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[10] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[11] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[12]; + a1 = a[13]; + a2 = a[14]; + a3 = a[15]; + tmp[12] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[13] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[14] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[15] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + memcpy(m, tmp, sizeof(GLfloat) * 4 * 4); +} + +void LoadIdentity(GLfloat* m) { + memset(m, 0, sizeof(GLfloat) * 4 * 4); + m[0] = m[5] = m[10] = m[15] = 1.0f; +} + +} // namespace transform_4x4 + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h new file mode 100644 index 0000000..5ac3d6e --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TRANSFORMS_H_ +#define EXAMPLES_TUMBLER_TRANSFORMS_H_ + +#include <GLES2/gl2.h> + +// A very simple set of 4x4 matrix routines. In all these routines, the input +// matrix is assumed to be a 4x4 of GLfloats. + +namespace transform_4x4 { + +// Pre-multply |m| with a projection transformation 4x4 matrix from a +// truncated pyramid viewing frustum. +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z); + +// Replace |m| with the 4x4 identity matrix. +void LoadIdentity(GLfloat* m); + +// |m| <- |a| . |b|. |m| can point at the same memory as either |a| or |b|. +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b); + +// Pre-multiply |m| with a single-point perspective matrix based on the viewing +// frustum whose view angle is |fovy|. +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z); + +// Pre-multiply |m| with a matrix that represents a translation by |tx|, |ty|, +// |tz|. +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz); +} // namespace transform_4x4 + +#endif // EXAMPLES_TUMBLER_TRANSFORMS_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc new file mode 100644 index 0000000..3c38639 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/tumbler.h" + +#include <stdio.h> + +#include <cstdlib> +#include <cstring> +#include <string> +#include <vector> + +#include "examples/fullscreen_tumbler/cube.h" +#include "examples/fullscreen_tumbler/opengl_context.h" +#include "examples/fullscreen_tumbler/scripting_bridge.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" +#include "ppapi/cpp/var.h" + +namespace { +const uint32_t kKeyEnter = 0x0D; +const size_t kQuaternionElementCount = 4; +const char* const kArrayStartCharacter = "["; +const char* const kArrayEndCharacter = "]"; +const char* const kArrayDelimiter = ","; + +// Return the value of parameter named |param_name| from |parameters|. If +// |param_name| doesn't exist, then return an empty string. +std::string GetParameterNamed( + const std::string& param_name, + const tumbler::MethodParameter& parameters) { + tumbler::MethodParameter::const_iterator i = + parameters.find(param_name); + if (i == parameters.end()) { + return ""; + } + return i->second; +} + +// Convert the JSON string |array| into a vector of floats. |array| is +// expected to be a string bounded by '[' and ']' and containing a +// comma-delimited list of numbers. Any errors result in the return of an +// empty array. +std::vector<float> CreateArrayFromJSON(const std::string& json_array) { + std::vector<float> float_array; + size_t array_start_pos = json_array.find_first_of(kArrayStartCharacter); + size_t array_end_pos = json_array.find_last_of(kArrayEndCharacter); + if (array_start_pos == std::string::npos || + array_end_pos == std::string::npos) + return float_array; // Malformed JSON: missing '[' or ']'. + // Pull out the array elements. + size_t token_pos = array_start_pos + 1; + while (token_pos < array_end_pos) { + float_array.push_back(strtof(json_array.data() + token_pos, NULL)); + size_t delim_pos = json_array.find_first_of(kArrayDelimiter, token_pos); + if (delim_pos == std::string::npos) + break; + token_pos = delim_pos + 1; + } + return float_array; +} +} // namespace + +namespace tumbler { + +Tumbler::Tumbler(PP_Instance instance) + : pp::Instance(instance), + full_screen_(this), + has_focus_(false), + cube_(NULL) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); +} + +Tumbler::~Tumbler() { + // Destroy the cube view while GL context is current. + opengl_context_->MakeContextCurrent(this); + delete cube_; +} + +bool Tumbler::Init(uint32_t /* argc */, + const char* /* argn */[], + const char* /* argv */[]) { + // Add all the methods to the scripting bridge. + ScriptingBridge::SharedMethodCallbackExecutor set_orientation_method( + new tumbler::MethodCallback<Tumbler>( + this, &Tumbler::SetCameraOrientation)); + scripting_bridge_.AddMethodNamed("setCameraOrientation", + set_orientation_method); + return true; +} + +void Tumbler::HandleMessage(const pp::Var& message) { + if (!message.is_string()) + return; + scripting_bridge_.InvokeMethod(message.AsString()); +} + +bool Tumbler::HandleInputEvent(const pp::InputEvent& event) { + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_UNDEFINED: + break; + case PP_INPUTEVENT_TYPE_MOUSEDOWN: + // If we do not yet have focus, return true. In return Chrome will give + // focus to the NaCl embed. + return !has_focus_; + break; + case PP_INPUTEVENT_TYPE_KEYDOWN: + HandleKeyDownEvent(pp::KeyboardInputEvent(event)); + break; + case PP_INPUTEVENT_TYPE_MOUSEUP: + case PP_INPUTEVENT_TYPE_MOUSEMOVE: + case PP_INPUTEVENT_TYPE_MOUSEENTER: + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + case PP_INPUTEVENT_TYPE_WHEEL: + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + case PP_INPUTEVENT_TYPE_KEYUP: + case PP_INPUTEVENT_TYPE_CHAR: + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + case PP_INPUTEVENT_TYPE_IME_TEXT: + default: + return false; + } + return false; +} + +void Tumbler::DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + // Note: When switching to fullscreen, the new View position will be a + // rectangle that encompasses the entire screen - e.g. 1900x1200 - with its + // top-left corner at (0, 0). When switching back to the windowed screen the + // position returns to what it was before going to fullscreen. + int cube_width = cube_ ? cube_->width() : 0; + int cube_height = cube_ ? cube_->height() : 0; + if (position.size().width() == cube_width && + position.size().height() == cube_height) { + return; // Size didn't change, no need to update anything. + } + + if (opengl_context_ == NULL) + opengl_context_.reset(new OpenGLContext(this)); + opengl_context_->InvalidateContext(this); + opengl_context_->ResizeContext(position.size()); + if (!opengl_context_->MakeContextCurrent(this)) + return; + if (cube_ == NULL) { + cube_ = new Cube(opengl_context_); + cube_->PrepareOpenGL(); + } + cube_->Resize(position.size().width(), position.size().height()); + DrawSelf(); +} + +void Tumbler::DidChangeFocus(bool focus) { + has_focus_ = focus; +} + +void Tumbler::DrawSelf() { + if (cube_ == NULL || opengl_context_ == NULL) + return; + opengl_context_->MakeContextCurrent(this); + cube_->Draw(); + opengl_context_->FlushContext(); +} + +void Tumbler::HandleKeyDownEvent(const pp::KeyboardInputEvent& key_event) { + // Pressing the Enter key toggles the view to/from full screen. + if (key_event.GetKeyCode() == kKeyEnter) { + if (!full_screen_.IsFullscreen()) { + if (!full_screen_.SetFullscreen(true)) { + printf("Failed to switch to fullscreen mode.\n"); + } + } else { + if (!full_screen_.SetFullscreen(false)) { + printf("Failed to switch to normal mode.\n"); + } + } + } +} + +void Tumbler::SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters) { + // |parameters| is expected to contain one object named "orientation", whose + // value is a JSON string that represents an array of four floats. + if (parameters.size() != 1 || cube_ == NULL) + return; + std::string orientation_desc = GetParameterNamed("orientation", parameters); + std::vector<float> orientation = CreateArrayFromJSON(orientation_desc); + if (orientation.size() != kQuaternionElementCount) { + return; + } + cube_->SetOrientation(orientation); + DrawSelf(); +} + +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h new file mode 100644 index 0000000..d752ea3 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h @@ -0,0 +1,83 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TUMBLER_H_ +#define EXAMPLES_TUMBLER_TUMBLER_H_ + +#include <pthread.h> +#include <map> +#include <vector> + +#include "examples/fullscreen_tumbler/cube.h" +#include "examples/fullscreen_tumbler/opengl_context.h" +#include "examples/fullscreen_tumbler/opengl_context_ptrs.h" +#include "examples/fullscreen_tumbler/scripting_bridge.h" +#include "ppapi/cpp/fullscreen.h" +#include "ppapi/cpp/instance.h" + +namespace pp { +class KeyboardInputEvent; +} // namespace pp + + +namespace tumbler { + +class Tumbler : public pp::Instance { + public: + explicit Tumbler(PP_Instance instance); + + // The dtor makes the 3D context current before deleting the cube view, then + // destroys the 3D context both in the module and in the browser. + virtual ~Tumbler(); + + // Called by the browser when the NaCl module is loaded and all ready to go. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called whenever the in-browser window changes size. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser when the NaCl canvas gets or loses focus. + virtual void DidChangeFocus(bool has_focus); + + // Called by the browser to handle the postMessage() call in Javascript. + virtual void HandleMessage(const pp::Var& message); + + // Called by the browser to handle incoming input events. + virtual bool HandleInputEvent(const pp::InputEvent& event); + + // Bind and publish the module's methods to JavaScript. + void InitializeMethods(ScriptingBridge* bridge); + + // Set the camera orientation to the quaternion in |args[0]|. |args| must + // have length at least 1; the first element is expeted to be an Array + // object containing 4 floating point number elements (the quaternion). + // This method is bound to the JavaScript "setCameraOrientation" method and + // is called like this: + // module.setCameraOrientation([0.0, 1.0, 0.0, 0.0]); + void SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters); + + // Called to draw the contents of the module's browser area. + void DrawSelf(); + + private: + // Process key-down input events. + void HandleKeyDownEvent(const pp::KeyboardInputEvent& key_event); + + pp::Fullscreen full_screen_; + bool has_focus_; + + // Browser connectivity and scripting support. + ScriptingBridge scripting_bridge_; + + SharedOpenGLContext opengl_context_; + // Wouldn't it be awesome if we had boost::scoped_ptr<>? + Cube* cube_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_TUMBLER_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js new file mode 100644 index 0000000..58096e4 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview The tumbler Application object. This object instantiates a + * Trackball object and connects it to the element named |tumbler_content|. + * It also conditionally embeds a debuggable module or a release module into + * the |tumbler_content| element. + */ + +// Requires tumbler +// Requires tumbler.Dragger +// Requires tumbler.Trackball + +/** + * Constructor for the Application class. Use the run() method to populate + * the object with controllers and wire up the events. + * @constructor + */ +tumbler.Application = function() { + /** + * The native module for the application. This refers to the module loaded + * using the <embed> tag. + * @type {Element} + * @private + */ + this.module_ = null; + + /** + * The trackball object. + * @type {tumbler.Trackball} + * @private + */ + this.trackball_ = null; + + /** + * The mouse-drag event object. + * @type {tumbler.Dragger} + * @private + */ + this.dragger_ = null; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed when they are no longer needed. + * @type {function} + * @private + */ + this.boundModuleDidLoad_ = null; +} + +/** + * The ids used for elements in the DOM. The Tumbler Application expects these + * elements to exist. + * @enum {string} + * @private + */ +tumbler.Application.DomIds_ = { + MODULE: 'tumbler', // The <embed> element representing the NaCl module + VIEW: 'tumbler_view' // The <div> containing the NaCl element. +} + +/** + * Called by the module loading function once the module has been loaded. + * @param {?Element} nativeModule The instance of the native module. + */ +tumbler.Application.prototype.moduleDidLoad = function() { + this.module_ = document.getElementById(tumbler.Application.DomIds_.MODULE); + // Unbind the load function. + this.boundModuleDidLoad_ = null; + + /** + * Set the camera orientation property on the NaCl module. + * @param {Array.<number>} orientation A 4-element array representing the + * camera orientation as a quaternion. + */ + this.module_.setCameraOrientation = function(orientation) { + var methodString = 'setCameraOrientation ' + + 'orientation:' + + JSON.stringify(orientation); + this.postMessage(methodString); + } + + this.trackball_ = new tumbler.Trackball(); + this.dragger_ = new tumbler.Dragger(this.module_); + this.dragger_.addDragListener(this.trackball_); +} + +/** + * Asserts that cond is true; issues an alert and throws an Error otherwise. + * @param {bool} cond The condition. + * @param {String} message The error message issued if cond is false. + */ +tumbler.Application.prototype.assert = function(cond, message) { + if (!cond) { + message = "Assertion failed: " + message; + alert(message); + throw new Error(message); + } +} + +/** + * The run() method starts and 'runs' the application. The trackball object + * is allocated and all the events get wired up. + * @param {?String} opt_contentDivName The id of a DOM element in which to + * embed the Native Client module. If unspecified, defaults to + * VIEW. The DOM element must exist. + */ +tumbler.Application.prototype.run = function(opt_contentDivName) { + contentDivName = opt_contentDivName || tumbler.Application.DomIds_.VIEW; + var contentDiv = document.getElementById(contentDivName); + this.assert(contentDiv, "Missing DOM element '" + contentDivName + "'"); + + // Note that the <EMBED> element is wrapped inside a <DIV>, which has a 'load' + // event listener attached. This method is used instead of attaching the + // 'load' event listener directly to the <EMBED> element to ensure that the + // listener is active before the NaCl module 'load' event fires. + this.boundModuleDidLoad_ = this.moduleDidLoad.bind(this); + contentDiv.addEventListener('load', this.boundModuleDidLoad_, true); + + // Load the published .nexe. This includes the 'nacl' attribute which + // shows how to load multi-architecture modules. Each entry in the "nexes" + // object in the .nmf manifest file is a key-value pair: the key is the + // runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired + // NaCl module. To load the debug versions of your .nexes, set the 'nacl' + // attribute to the _dbg.nmf version of the manifest file. + contentDiv.innerHTML = '<embed id="' + + tumbler.Application.DomIds_.MODULE + '" ' + + 'src=fullscreen_tumbler.nmf ' + + 'type="application/x-nacl" ' + + 'width="480" height="480" />' +} diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc new file mode 100644 index 0000000..37e8bd0 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/tumbler.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with type="application/x-nacl". +class TumberModule : public pp::Module { + public: + TumberModule() : pp::Module() {} + virtual ~TumberModule() { + glTerminatePPAPI(); + } + + /// Called by the browser when the module is first loaded and ready to run. + /// This is called once per module, not once per instance of the module on + /// the page. + virtual bool Init() { + return glInitializePPAPI(get_browser_interface()) == GL_TRUE; + } + + /// Create and return a Tumbler instance object. + /// @param[in] instance The browser-side instance. + /// @return the plugin-side instance. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new tumbler::Tumbler(instance); + } +}; + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +Module* CreateModule() { + return new TumberModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js b/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js new file mode 100644 index 0000000..a79f781 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview A 3D vector class. Proviudes some utility functions on + * 3-dimentional vectors. + */ + +// Requires tumbler + +/** + * Constructor for the Vector3 object. This class contains a 3-tuple that + * represents a vector in 3D space. + * @param {?number} opt_x The x-coordinate for this vector. If null or + * undefined, the x-coordinate value is set to 0. + * @param {?number} opt_y The y-coordinate for this vector. If null or + * undefined, the y-coordinate value is set to 0. + * @param {?number} opt_z The z-coordinate for this vector. If null or + * undefined, the z-coordinate value is set to 0. + * @constructor + */ +tumbler.Vector3 = function(opt_x, opt_y, opt_z) { + /** + * The vector's 3-tuple. + * @type {number} + */ + this.x = opt_x || 0; + this.y = opt_y || 0; + this.z = opt_z || 0; +} + +/** + * Method to return the magnitude of a Vector3. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.magnitude = function() { + return Math.sqrt(this.dot(this)); +} + +/** + * Normalize the vector in-place. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.normalize = function() { + var mag = this.magnitude(); + if (mag < tumbler.Vector3.DOUBLE_EPSILON) + return 0.0; // |this| is equivalent to the 0-vector, don't normalize. + this.scale(1.0 / mag); + return mag; +} + +/** + * Scale the vector in-place by |s|. + * @param {!number} s The scale factor. + */ +tumbler.Vector3.prototype.scale = function(s) { + this.x *= s; + this.y *= s; + this.z *= s; +} + +/** + * Compute the dot product: |this| . v. + * @param {!tumbler.Vector3} v The vector to dot. + * @return {number} the result of |this| . v. + */ +tumbler.Vector3.prototype.dot = function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; +} + +/** + * Compute the cross product: |this| X v. + * @param {!tumbler.Vector3} v The vector to cross with. + * @return {tumbler.Vector3} the result of |this| X v. + */ +tumbler.Vector3.prototype.cross = function(v) { + var vCross = new tumbler.Vector3(this.y * v.z - this.z * v.y, + this.z * v.x - this.x * v.z, + this.x * v.y - this.y * v.x); + return vCross; +} + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in generally available somewhere. + * @type {number} + */ +tumbler.Vector3.DOUBLE_EPSILON = 1.0e-16; diff --git a/native_client_sdk/src/examples/geturl/build.scons b/native_client_sdk/src/examples/geturl/build.scons new file mode 100644 index 0000000..f855644 --- /dev/null +++ b/native_client_sdk/src/examples/geturl/build.scons @@ -0,0 +1,41 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='geturl', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['geturl.cc', 'geturl_handler.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'geturl') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('geturl') + +app_files = [ + 'geturl.html', + 'geturl_success.html', + 'geturl.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/geturl/geturl.cc b/native_client_sdk/src/examples/geturl/geturl.cc new file mode 100644 index 0000000..48d5cbb --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This example demonstrates how to load content of the page into NaCl module. + +#include <cstdio> +#include <string> +#include "examples/geturl/geturl_handler.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/url_loader.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +// These are the method names as JavaScript sees them. +namespace { +const char* const kLoadUrlMethodId = "getUrl"; +static const char kMessageArgumentSeparator = ':'; + +// Exception strings. These are passed back to the browser when errors +// happen during property accesses or method calls. +const char* const kExceptionStartFailed = "GetURLHandler::Start() failed"; +const char* const kExceptionURLNotAString = "URL is not a string"; +} // namespace + +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// src="geturl.nmf" +class GetURLInstance : public pp::Instance { + public: + explicit GetURLInstance(PP_Instance instance) : pp::Instance(instance) {} + virtual ~GetURLInstance() {} + + // Called by the browser to handle the postMessage() call in Javascript. + // The message in this case is expected to contain the string 'getUrl' + // followed by a ':' separator, then the URL to fetch. If a valid message + // of the form 'getUrl:URL' is received, then start up an asynchronous + // download of URL. In the event that errors occur, this method posts an + // error string back to the browser. + virtual void HandleMessage(const pp::Var& var_message); +}; + +void GetURLInstance::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + return; + } + std::string message = var_message.AsString(); + if (message.find(kLoadUrlMethodId) == 0) { + // The argument to getUrl is everything after the first ':'. + size_t sep_pos = message.find_first_of(kMessageArgumentSeparator); + if (sep_pos != std::string::npos) { + std::string url = message.substr(sep_pos + 1); + printf("GetURLInstance::HandleMessage('%s', '%s')\n", + message.c_str(), + url.c_str()); + fflush(stdout); + GetURLHandler* handler = GetURLHandler::Create(this, url); + if (handler != NULL) { + // Starts asynchronous download. When download is finished or when an + // error occurs, |handler| posts the results back to the browser + // vis PostMessage and self-destroys. + handler->Start(); + } + } + } +} + + +// The Module class. The browser calls the CreateInstance() method to create +// an instance of you NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class GetURLModule : public pp::Module { + public: + GetURLModule() : pp::Module() {} + virtual ~GetURLModule() {} + + // Create and return a GetURLInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new GetURLInstance(instance); + } +}; + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new GetURLModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/geturl/geturl.html b/native_client_sdk/src/examples/geturl/geturl.html new file mode 100644 index 0000000..c4a0c79 --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl.html @@ -0,0 +1,125 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Get URL</title> + + <script type="text/javascript"> + geturlModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + geturlModule = document.getElementById('geturl'); + updateStatus('SUCCESS'); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + if (geturl == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + // Called from the NaCl module via PostMessage(). The message data + // contains a URL followed by a '\n' separator character and the result + // text. The result test itself can contain '\n' characters, only the first + // '\n' is considered when separating the message parameters. + function handleMessage(message_event) { + var logElt = document.getElementById('general_output'); + // Find the first line break. This separates the URL data from the + // result text. Note that the result text can contain any number of + // '\n' characters, so split() won't work here. + var url = message_event.data; + var result = ''; + var eol_pos = message_event.data.indexOf("\n"); + if (eol_pos != -1) { + url = message_event.data.substring(0, eol_pos); + if (eol_pos < message_event.data.length - 1) { + result = message_event.data.substring(eol_pos + 1); + } + } + logElt.textContent += 'FULLY QUALIFIED URL: ' + url + '\n'; + logElt.textContent += 'RESULT:\n' + result + '\n'; + } + + function loadUrl() { + geturlModule.postMessage('getUrl:geturl_success.html'); + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // @param opt_message The message text. If this is null or undefined, then + // attempt to set the element with id 'status_field' to the value of + // @a statusText. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('status_field'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client GetURL Module</h1> +<p> +<table border=5 cellpadding=5% summary="A title and a result log"> + <tr> + <td valign=top><pre id='general_output' class='notrun'></pre></td> + </tr> +</table> + + <form name="geturl_form" action="" method="get"> + <input type="button" value="Get URL" onclick="loadUrl()"/> + </form> + + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="geturl" + width=0 height=0 + src="geturl.nmf" + type="application/x-nacl" /> + </div> +</p> + +<h2>Module loading status</h2> +<div id="status_field">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/geturl/geturl_handler.cc b/native_client_sdk/src/examples/geturl/geturl_handler.cc new file mode 100644 index 0000000..f6536fc --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl_handler.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2011 The Native Client 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 "examples/geturl/geturl_handler.h" + +#include <stdio.h> +#include <stdlib.h> +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace { +bool IsError(int32_t result) { + return ((PP_OK != result) && (PP_OK_COMPLETIONPENDING != result)); +} +} // namespace + +GetURLHandler* GetURLHandler::Create(pp::Instance* instance, + const std::string& url) { + return new GetURLHandler(instance, url); +} + +GetURLHandler::GetURLHandler(pp::Instance* instance, + const std::string& url) + : instance_(instance), + url_(url), + url_request_(instance), + url_loader_(instance), + cc_factory_(this) { + url_request_.SetURL(url); + url_request_.SetMethod("GET"); +} + +GetURLHandler::~GetURLHandler() { +} + +void GetURLHandler::Start() { + pp::CompletionCallback cc = + cc_factory_.NewRequiredCallback(&GetURLHandler::OnOpen); + url_loader_.Open(url_request_, cc); +} + +void GetURLHandler::OnOpen(int32_t result) { + if (result != PP_OK) { + ReportResultAndDie(url_, "pp::URLLoader::Open() failed", false); + return; + } + // Here you would process the headers. A real program would want to at least + // check the HTTP code and potentially cancel the request. + // pp::URLResponseInfo response = loader_.GetResponseInfo(); + + // Start streaming. + ReadBody(); +} + +void GetURLHandler::AppendDataBytes(const char* buffer, int32_t num_bytes) { + if (num_bytes <= 0) + return; + // Make sure we don't get a buffer overrun. + num_bytes = std::min(READ_BUFFER_SIZE, num_bytes); + url_response_body_.reserve(url_response_body_.size() + num_bytes); + url_response_body_.insert(url_response_body_.end(), + buffer, + buffer + num_bytes); +} + +void GetURLHandler::OnRead(int32_t result) { + if (result == PP_OK) { + // Streaming the file is complete. + ReportResultAndDie(url_, url_response_body_, true); + } else if (result > 0) { + // The URLLoader just filled "result" number of bytes into our buffer. + // Save them and perform another read. + AppendDataBytes(buffer_, result); + ReadBody(); + } else { + // A read error occurred. + ReportResultAndDie(url_, + "pp::URLLoader::ReadResponseBody() result<0", + false); + } +} + +void GetURLHandler::ReadBody() { + // Note that you specifically want an "optional" callback here. This will + // allow ReadBody() to return synchronously, ignoring your completion + // callback, if data is available. For fast connections and large files, + // reading as fast as we can will make a large performance difference + // However, in the case of a synchronous return, we need to be sure to run + // the callback we created since the loader won't do anything with it. + pp::CompletionCallback cc = + cc_factory_.NewOptionalCallback(&GetURLHandler::OnRead); + int32_t result = PP_OK; + do { + result = url_loader_.ReadResponseBody(buffer_, sizeof(buffer_), cc); + // Handle streaming data directly. Note that we *don't* want to call + // OnRead here, since in the case of result > 0 it will schedule + // another call to this function. If the network is very fast, we could + // end up with a deeply recursive stack. + if (result > 0) { + AppendDataBytes(buffer_, result); + } + } while (result > 0); + + if (result != PP_OK_COMPLETIONPENDING) { + // Either we reached the end of the stream (result == PP_OK) or there was + // an error. We want OnRead to get called no matter what to handle + // that case, whether the error is synchronous or asynchronous. If the + // result code *is* COMPLETIONPENDING, our callback will be called + // asynchronously. + cc.Run(result); + } +} + +void GetURLHandler::ReportResultAndDie(const std::string& fname, + const std::string& text, + bool success) { + ReportResult(fname, text, success); + delete this; +} + +void GetURLHandler::ReportResult(const std::string& fname, + const std::string& text, + bool success) { + if (success) + printf("GetURLHandler::ReportResult(Ok).\n"); + else + printf("GetURLHandler::ReportResult(Err). %s\n", text.c_str()); + fflush(stdout); + if (instance_) { + pp::Var var_result(fname + "\n" + text); + instance_->PostMessage(var_result); + } +} + diff --git a/native_client_sdk/src/examples/geturl/geturl_handler.h b/native_client_sdk/src/examples/geturl/geturl_handler.h new file mode 100644 index 0000000..928f22c --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl_handler.h @@ -0,0 +1,81 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_GETURL_GETURL_HANDLER_H_ +#define EXAMPLES_GETURL_GETURL_HANDLER_H_ + +#include <string> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/url_loader.h" +#include "ppapi/cpp/url_request_info.h" +#include "ppapi/cpp/instance.h" + +#define READ_BUFFER_SIZE 4096 + +// GetURLHandler is used to download data from |url|. When download is +// finished or when an error occurs, it posts a message back to the browser +// with the results encoded in the message as a string and self-destroys. +// +// EXAMPLE USAGE: +// GetURLHandler* handler* = GetURLHandler::Create(instance,url); +// handler->Start(); +// +class GetURLHandler { + public: + // Creates instance of GetURLHandler on the heap. + // GetURLHandler objects shall be created only on the heap (they + // self-destroy when all data is in). + static GetURLHandler* Create(pp::Instance* instance_, + const std::string& url); + // Initiates page (URL) download. + void Start(); + + private: + GetURLHandler(pp::Instance* instance_, const std::string& url); + ~GetURLHandler(); + + // Callback fo the pp::URLLoader::Open(). + // Called by pp::URLLoader when response headers are received or when an + // error occurs (in response to the call of pp::URLLoader::Open()). + // Look at <ppapi/c/ppb_url_loader.h> and + // <ppapi/cpp/url_loader.h> for more information about pp::URLLoader. + void OnOpen(int32_t result); + + // Callback fo the pp::URLLoader::ReadResponseBody(). + // |result| contains the number of bytes read or an error code. + // Appends data from this->buffer_ to this->url_response_body_. + void OnRead(int32_t result); + + // Reads the response body (asynchronously) into this->buffer_. + // OnRead() will be called when bytes are received or when an error occurs. + void ReadBody(); + + // Append data bytes read from the URL onto the internal buffer. Does + // nothing if |num_bytes| is 0. + void AppendDataBytes(const char* buffer, int32_t num_bytes); + + // Post a message back to the browser with the download results. + void ReportResult(const std::string& fname, + const std::string& text, + bool success); + // Post a message back to the browser with the download results and + // self-destroy. |this| is no longer valid when this method returns. + void ReportResultAndDie(const std::string& fname, + const std::string& text, + bool success); + + pp::Instance* instance_; // Weak pointer. + std::string url_; // URL to be downloaded. + pp::URLRequestInfo url_request_; + pp::URLLoader url_loader_; // URLLoader provides an API to download URLs. + char buffer_[READ_BUFFER_SIZE]; // Temporary buffer for reads. + std::string url_response_body_; // Contains accumulated downloaded data. + pp::CompletionCallbackFactory<GetURLHandler> cc_factory_; + + GetURLHandler(const GetURLHandler&); + void operator=(const GetURLHandler&); +}; + +#endif // EXAMPLES_GETURL_GETURL_HANDLER_H_ + diff --git a/native_client_sdk/src/examples/geturl/geturl_success.html b/native_client_sdk/src/examples/geturl/geturl_success.html new file mode 100644 index 0000000..8f2f112 --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl_success.html @@ -0,0 +1,20 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2010 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>PPAPI geturl example</title> + <META HTTP-EQUIV="Pragma" CONTENT="no-cache" /> + <META HTTP-EQUIV="Expires" CONTENT="-1" /> + </head> + <body> + <h1>PPAPI geturl example</h1> + The PPAPI geturl example fetches the contents of this page. + If you are seeing the contents of this page as part of the test output, + then the test passed. + </body> +</html> diff --git a/native_client_sdk/src/examples/hello_world/build.scons b/native_client_sdk/src/examples/hello_world/build.scons new file mode 100644 index 0000000..51dc5df --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/build.scons @@ -0,0 +1,59 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='hello_world', lib_prefix='..') +nacl_test_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, + nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + use_ppapi=False) +for env in [nacl_env, nacl_test_env]: + env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + CCFLAGS=['-pedantic', '-Werror'], + ) + +sources = ['hello_world.cc', 'helper_functions.cc'] +test_sources = ['helper_functions.cc', 'test_helper_functions.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'hello_world') + +nacl_test_32 = nacl_test_env.Clone() +nacl_test_32.NaClTestProgram(test_sources, + nacl_utils.ARCH_SPECS['x86-32'], + module_name='hello_world_test', + target_name='test32') + +nacl_test_64 = nacl_test_env.Clone() +nacl_test_64.NaClTestProgram(test_sources, + nacl_utils.ARCH_SPECS['x86-64'], + module_name='hello_world_test', + target_name='test64') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('hello_world') + +app_files = [ + 'hello_world.html', + 'hello_world.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/hello_world/hello_world.cc b/native_client_sdk/src/examples/hello_world/hello_world.cc new file mode 100644 index 0000000..df67961 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/hello_world.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// @file +/// This example demonstrates loading, running and scripting a very simple NaCl +/// module. To load the NaCl module, the browser first looks for the +/// CreateModule() factory method (at the end of this file). It calls +/// CreateModule() once to load the module code from your .nexe. After the +/// .nexe code is loaded, CreateModule() is not called again. +/// +/// Once the .nexe code is loaded, the browser then calls the +/// HelloWorldModule::CreateInstance() +/// method on the object returned by CreateModule(). It calls CreateInstance() +/// each time it encounters an <embed> tag that references your NaCl module. + +#include <cstdio> +#include <cstring> +#include <string> +#include "examples/hello_world/helper_functions.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace hello_world { +/// Method name for ReverseText, as seen by JavaScript code. +const char* const kReverseTextMethodId = "reverseText"; + +/// Method name for FortyTwo, as seen by Javascript code. @see FortyTwo() +const char* const kFortyTwoMethodId = "fortyTwo"; + +/// Separator character for the reverseText method. +static const char kMessageArgumentSeparator = ':'; + +/// This is the module's function that invokes FortyTwo and converts the return +/// value from an int32_t to a pp::Var for return. +pp::Var MarshallFortyTwo() { + return pp::Var(FortyTwo()); +} + +/// This function is passed the arg list from the JavaScript call to +/// @a reverseText. +/// It makes sure that there is one argument and that it is a string, returning +/// an error message if it is not. +/// On good input, it calls ReverseText and returns the result. The result is +/// then sent back via a call to PostMessage. +pp::Var MarshallReverseText(const std::string& text) { + return pp::Var(ReverseText(text)); +} + +/// The Instance class. One of these exists for each instance of your NaCl +/// module on the web page. The browser will ask the Module object to create +/// a new Instance for each occurrence of the <embed> tag that has these +/// attributes: +/// <pre> +/// type="application/x-nacl" +/// nacl="hello_world.nmf" +/// </pre> +class HelloWorldInstance : public pp::Instance { + public: + explicit HelloWorldInstance(PP_Instance instance) : pp::Instance(instance) {} + virtual ~HelloWorldInstance() {} + + /// Called by the browser to handle the postMessage() call in Javascript. + /// Detects which method is being called from the message contents, and + /// calls the appropriate function. Posts the result back to the browser + /// asynchronously. + /// @param[in] var_message The message posted by the browser. The possible + /// messages are 'fortyTwo' and 'reverseText:Hello World'. Note that + /// the 'reverseText' form contains the string to reverse following a ':' + /// separator. + virtual void HandleMessage(const pp::Var& var_message); +}; + +void HelloWorldInstance::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + return; + } + std::string message = var_message.AsString(); + pp::Var return_var; + if (message == kFortyTwoMethodId) { + // Note that no arguments are passed in to FortyTwo. + return_var = MarshallFortyTwo(); + } else if (message.find(kReverseTextMethodId) == 0) { + // The argument to reverseText is everything after the first ':'. + size_t sep_pos = message.find_first_of(kMessageArgumentSeparator); + if (sep_pos != std::string::npos) { + std::string string_arg = message.substr(sep_pos + 1); + return_var = MarshallReverseText(string_arg); + } + } + // Post the return result back to the browser. Note that HandleMessage() is + // always called on the main thread, so it's OK to post the return message + // directly from here. The return post is asynhronous: PostMessage returns + // immediately. + PostMessage(return_var); +} + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with +/// <code>type="application/x-nacl"</code>. +class HelloWorldModule : public pp::Module { + public: + HelloWorldModule() : pp::Module() {} + virtual ~HelloWorldModule() {} + + /// Create and return a HelloWorldInstance object. + /// @param[in] instance a handle to a plug-in instance. + /// @return a newly created HelloWorldInstance. + /// @note The browser is responsible for calling @a delete when done. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new HelloWorldInstance(instance); + } +}; +} // namespace hello_world + + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +/// @return new HelloWorldModule. +/// @note The browser is responsible for deleting returned @a Module. +Module* CreateModule() { + return new hello_world::HelloWorldModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/hello_world/hello_world.html b/native_client_sdk/src/examples/hello_world/hello_world.html new file mode 100644 index 0000000..16dc42c --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/hello_world.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Hello, World!</title> + + <script type="text/javascript"> + helloWorldModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + helloWorldModule = document.getElementById('hello_world'); + updateStatus('SUCCESS'); + } + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + // Set the focus on the text input box. Doing this means you can press + // return as soon as the page loads, and it will fire the reversetText() + // function. + document.forms.helloForm.inputBox.focus(); + if (helloWorldModule == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + function fortyTwo() { + helloWorldModule.postMessage('fortyTwo'); + } + + function reverseText() { + // Grab the text from the text box, pass it into reverseText() + var inputBox = document.forms.helloForm.inputBox; + helloWorldModule.postMessage('reverseText:' + inputBox.value); + // Note: a |false| return tells the <form> tag to cancel the GET action + // when submitting the form. + return false; + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Simple Module</h1> +<p> + <form name="helloForm" + action="" + method="get" + onsubmit="return reverseText()"> + <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p/> + <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" /> + <input type="submit" value="Call reverseText()" /> + </form> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="hello_world" + width=0 height=0 + src="hello_world.nmf" + type="application/x-nacl" /> + </div> + +</p> + +<p>If the module is working correctly, a click on the "Call fortyTwo()" button + should open a popup dialog containing <b>42</b> as its value.</p> + +<p> Clicking on the "Call reverseText()" button + should open a popup dialog containing the textbox contents and its reverse + as its value.</p> + +<h2>Status</h2> +<div id="statusField">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/hello_world/helper_functions.cc b/native_client_sdk/src/examples/hello_world/helper_functions.cc new file mode 100644 index 0000000..5ed26b5 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/helper_functions.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client 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 "examples/hello_world/helper_functions.h" + +#include <algorithm> + +namespace hello_world { + +int32_t FortyTwo() { + return 42; +} + +std::string ReverseText(const std::string& text) { + std::string reversed_string(text); + // Use reverse to reverse |reversed_string| in place. + std::reverse(reversed_string.begin(), reversed_string.end()); + return reversed_string; +} +} // namespace hello_world + diff --git a/native_client_sdk/src/examples/hello_world/helper_functions.h b/native_client_sdk/src/examples/hello_world/helper_functions.h new file mode 100644 index 0000000..69ab874 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/helper_functions.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_ +#define EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_ + +/// @file +/// These functions are stand-ins for your complicated computations which you +/// want to run in native code. We do two very simple things: return 42, and +/// reverse a string. But you can imagine putting more complicated things here +/// which might be difficult or slow to achieve in JavaScript, such as +/// cryptography, artificial intelligence, signal processing, physics modeling, +/// etc. See hello_world.cc for the code which is required for loading a NaCl +/// application and exposing methods to JavaScript. + +#include <ppapi/c/pp_stdint.h> +#include <string> + +namespace hello_world { + +/// This is the module's function that does the work to compute the value 42. +int32_t FortyTwo(); + +/// This function is passed a string and returns a copy of the string with the +/// characters in reverse order. +/// @param[in] text The string to reverse. +/// @return A copy of @a text with the characters in reverse order. +std::string ReverseText(const std::string& text); + +} // namespace hello_world + +#endif // EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_ + diff --git a/native_client_sdk/src/examples/hello_world/test_helper_functions.cc b/native_client_sdk/src/examples/hello_world/test_helper_functions.cc new file mode 100644 index 0000000..f73f71a --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/test_helper_functions.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is an example of a simple unit test to verify that the logic helper +// functions works as expected. Note that this looks like a 'normal' C++ +// program, with a main function. It is compiled and linked using the NaCl +// toolchain, so in order to run it, you must use 'sel_ldr_x86_32' or +// 'sel_ldr_x86_64' from the toolchain's bin directory. +// +// For example (assuming the toolchain bin directory is in your path): +// sel_ldr_x86_32 test_helper_functions_x86_32_dbg.nexe +// +// You can also use the 'test32', or 'test64' SCons target to run these tests. +// For example, this will run the test in 32-bit mode on Mac or Linux: +// ../scons test32 +// On Windows 64: +// ..\scons test64 + +#include "examples/hello_world/helper_functions.h" + +#include <cassert> +#include <cstdio> +#include <string> + +// A very simple macro to print 'passed' if boolean_expression is true and +// 'FAILED' otherwise. +// This is meant to approximate the functionality you would get from a real test +// framework. You should feel free to build and use the test framework of your +// choice. +#define EXPECT_EQUAL(left, right)\ +printf("Check: \"" #left "\" == \"" #right "\" %s\n", \ + ((left) == (right)) ? "passed" : "FAILED") + +using hello_world::FortyTwo; +using hello_world::ReverseText; + +int main() { + EXPECT_EQUAL(FortyTwo(), 42); + + std::string empty_string; + EXPECT_EQUAL(ReverseText(empty_string), empty_string); + + std::string palindrome("able was i ere i saw elba"); + EXPECT_EQUAL(ReverseText(palindrome), palindrome); + + std::string alphabet("abcdefghijklmnopqrstuvwxyz"); + std::string alphabet_backwards("zyxwvutsrqponmlkjihgfedcba"); + EXPECT_EQUAL(ReverseText(alphabet), alphabet_backwards); +} + diff --git a/native_client_sdk/src/examples/hello_world_c/build.scons b/native_client_sdk/src/examples/hello_world_c/build.scons new file mode 100644 index 0000000..90666e5 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='hello_world_c', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['hello_world_c.c'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'hello_world_c') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('hello_world_c') + +app_files = [ + 'hello_world_c.html', + 'hello_world_c.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c.c b/native_client_sdk/src/examples/hello_world_c/hello_world_c.c new file mode 100644 index 0000000..939e724 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c.c @@ -0,0 +1,287 @@ +/* Copyright (c) 2011 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** @file hello_world.c + * This example demonstrates loading, running and scripting a very simple + * NaCl module. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_module.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/c/ppb_messaging.h" +#include "ppapi/c/ppb_var.h" +#include "ppapi/c/ppp.h" +#include "ppapi/c/ppp_instance.h" +#include "ppapi/c/ppp_messaging.h" + +struct MessageInfo { + PP_Instance instance; + struct PP_Var message; +}; + +static const char* const kReverseTextMethodId = "reverseText"; +static const char* const kFortyTwoMethodId = "fortyTwo"; +static const char kMessageArgumentSeparator = ':'; +static const char kNullTerminator = '\0'; + +static struct PPB_Messaging* ppb_messaging_interface = NULL; +static struct PPB_Var* ppb_var_interface = NULL; +static PP_Module module_id = 0; + + +/** + * Returns a mutable C string contained in the @a var or NULL if @a var is not + * string. This makes a copy of the string in the @ var and adds a NULL + * terminator. Note that VarToUtf8() does not guarantee the NULL terminator on + * the returned string. See the comments for VatToUtf8() in ppapi/c/ppb_var.h + * for more info. The caller is responsible for freeing the returned memory. + * @param[in] var PP_Var containing string. + * @return a C string representation of @a var. + * @note The caller is responsible for freeing the returned string. + */ +static char* VarToCStr(struct PP_Var var) { + uint32_t len = 0; + if (ppb_var_interface != NULL) { + const char* var_c_str = ppb_var_interface->VarToUtf8(var, &len); + if (len > 0) { + char* c_str = (char*)malloc(len + 1); + memcpy(c_str, var_c_str, len); + c_str[len] = kNullTerminator; + return c_str; + } + } + return NULL; +} + +/** + * Creates new string PP_Var from C string. The resulting object will be a + * refcounted string object. It will be AddRef()ed for the caller. When the + * caller is done with it, it should be Release()d. + * @param[in] str C string to be converted to PP_Var + * @return PP_Var containing string. + */ +static struct PP_Var CStrToVar(const char* str) { + if (ppb_var_interface != NULL) { + return ppb_var_interface->VarFromUtf8(module_id, str, strlen(str)); + } + return PP_MakeUndefined(); +} + +/** + * Reverse C string in-place. + * @param[in,out] str C string to be reversed + */ +static void ReverseStr(char* str) { + char* right = str + strlen(str) - 1; + char* left = str; + while (left < right) { + char tmp = *left; + *left++ = *right; + *right-- = tmp; + } +} + +/** + * A simple function that always returns 42. + * @return always returns the integer 42 + */ +static struct PP_Var FortyTwo() { + return PP_MakeInt32(42); +} + +/** + * Called when the NaCl module is instantiated on the web page. The identifier + * of the new instance will be passed in as the first argument (this value is + * generated by the browser and is an opaque handle). This is called for each + * instantiation of the NaCl module, which is each time the <embed> tag for + * this module is encountered. + * + * If this function reports a failure (by returning @a PP_FALSE), the NaCl + * module will be deleted and DidDestroy will be called. + * @param[in] instance The identifier of the new instance representing this + * NaCl module. + * @param[in] argc The number of arguments contained in @a argn and @a argv. + * @param[in] argn An array of argument names. These argument names are + * supplied in the <embed> tag, for example: + * <embed id="nacl_module" dimensions="2"> + * will produce two arguments, one named "id" and one named "dimensions". + * @param[in] argv An array of argument values. These are the values of the + * arguments listed in the <embed> tag. In the above example, there will + * be two elements in this array, "nacl_module" and "2". The indices of + * these values match the indices of the corresponding names in @a argn. + * @return @a PP_TRUE on success. + */ +static PP_Bool Instance_DidCreate(PP_Instance instance, + uint32_t argc, + const char* argn[], + const char* argv[]) { + return PP_TRUE; +} + +/** + * Called when the NaCl module is destroyed. This will always be called, + * even if DidCreate returned failure. This routine should deallocate any data + * associated with the instance. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + */ +static void Instance_DidDestroy(PP_Instance instance) { +} + +/** + * Called when the position, the size, or the clip rect of the element in the + * browser that corresponds to this NaCl module has changed. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] position The location on the page of this NaCl module. This is + * relative to the top left corner of the viewport, which changes as the + * page is scrolled. + * @param[in] clip The visible region of the NaCl module. This is relative to + * the top left of the plugin's coordinate system (not the page). If the + * plugin is invisible, @a clip will be (0, 0, 0, 0). + */ +static void Instance_DidChangeView(PP_Instance instance, + const struct PP_Rect* position, + const struct PP_Rect* clip) { +} + +/** + * Notification that the given NaCl module has gained or lost focus. + * Having focus means that keyboard events will be sent to the NaCl module + * represented by @a instance. A NaCl module's default condition is that it + * will not have focus. + * + * Note: clicks on NaCl modules will give focus only if you handle the + * click event. You signal if you handled it by returning @a true from + * HandleInputEvent. Otherwise the browser will bubble the event and give + * focus to the element on the page that actually did end up consuming it. + * If you're not getting focus, check to make sure you're returning true from + * the mouse click in HandleInputEvent. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] has_focus Indicates whether this NaCl module gained or lost + * event focus. + */ +static void Instance_DidChangeFocus(PP_Instance instance, + PP_Bool has_focus) { +} + +/** + * Handler that gets called after a full-frame module is instantiated based on + * registered MIME types. This function is not called on NaCl modules. This + * function is essentially a place-holder for the required function pointer in + * the PPP_Instance structure. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance. + * @return PP_FALSE. + */ +static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance, + PP_Resource url_loader) { + /* NaCl modules do not need to handle the document load function. */ + return PP_FALSE; +} + +/** + * Handler for messages coming in from the browser via postMessage. Extracts + * the method call from @a message, parses it for method name and value, then + * calls the appropriate function. In the case of the reverseString method, the + * message format is a simple colon-separated string. The first part of the + * string up to the colon is the method name; after that is the string argument. + * @param[in] instance The instance ID. + * @param[in] message The contents, copied by value, of the message sent from + * browser via postMessage. + */ +void Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message) { + if (var_message.type != PP_VARTYPE_STRING) { + /* Only handle string messages */ + return; + } + char* message = VarToCStr(var_message); + if (message == NULL) + return; + struct PP_Var var_result = PP_MakeUndefined(); + if (strncmp(message, kFortyTwoMethodId, strlen(kFortyTwoMethodId)) == 0) { + var_result = FortyTwo(); + } else if (strncmp(message, + kReverseTextMethodId, + strlen(kReverseTextMethodId)) == 0) { + /* Use everything after the ':' in |message| as the string argument. */ + char* string_arg = strchr(message, kMessageArgumentSeparator); + if (string_arg != NULL) { + string_arg += 1; /* Advance past the ':' separator. */ + ReverseStr(string_arg); + var_result = CStrToVar(string_arg); + } + } + free(message); + + /* Echo the return result back to browser. Note that HandleMessage is always + * called on the main thread, so it's OK to post the message back to the + * browser directly from here. This return post is asynchronous. + */ + ppb_messaging_interface->PostMessage(instance, var_result); + /* If the message was created using VarFromUtf8() it needs to be released. + * See the comments about VarFromUtf8() in ppapi/c/ppb_var.h for more + * information. + */ + if (var_result.type == PP_VARTYPE_STRING) { + ppb_var_interface->Release(var_result); + } +} + +/** + * Entry points for the module. + * Initialize needed interfaces: PPB_Core, PPB_Messaging and PPB_Var. + * @param[in] a_module_id module ID + * @param[in] get_browser pointer to PPB_GetInterface + * @return PP_OK on success, any other value on failure. + */ +PP_EXPORT int32_t PPP_InitializeModule(PP_Module a_module_id, + PPB_GetInterface get_browser) { + module_id = a_module_id; + ppb_messaging_interface = + (struct PPB_Messaging*)(get_browser(PPB_MESSAGING_INTERFACE)); + ppb_var_interface = (struct PPB_Var*)(get_browser(PPB_VAR_INTERFACE)); + + return PP_OK; +} + +/** + * Returns an interface pointer for the interface of the given name, or NULL + * if the interface is not supported. + * @param[in] interface_name name of the interface + * @return pointer to the interface + */ +PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { + if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) { + static struct PPP_Instance instance_interface = { + &Instance_DidCreate, + &Instance_DidDestroy, + &Instance_DidChangeView, + &Instance_DidChangeFocus, + &Instance_HandleDocumentLoad, + }; + return &instance_interface; + } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { + static struct PPP_Messaging messaging_interface = { + &Messaging_HandleMessage + }; + return &messaging_interface; + } + return NULL; +} + +/** + * Called before the plugin module is unloaded. + */ +PP_EXPORT void PPP_ShutdownModule() { +} diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c.html b/native_client_sdk/src/examples/hello_world_c/hello_world_c.html new file mode 100644 index 0000000..e5511c6 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Hello, World!</title> + + <script type="text/javascript"> + helloWorldModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + helloWorldModule = document.getElementById('hello_world'); + updateStatus('SUCCESS'); + } + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + // Set the focus on the text input box. Doing this means you can press + // return as soon as the page loads, and it will fire the reversetText() + // function. + document.forms.helloForm.inputBox.focus(); + if (helloWorldModule == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + function fortyTwo() { + helloWorldModule.postMessage('fortyTwo'); + } + + function reverseText() { + // Grab the text from the text box, pass it into reverseText() + var inputBox = document.forms.helloForm.inputBox; + helloWorldModule.postMessage('reverseText:' + inputBox.value); + // Note: a |false| return tells the <form> tag to cancel the GET action + // when submitting the form. + return false; + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Simple Module</h1> +<p> + <form name="helloForm" + action="" + method="get" + onsubmit="return reverseText()"> + <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p/> + <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" /> + <input type="submit" value="Call reverseText()" /> + </form> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="hello_world" + width=0 height=0 + src="hello_world_c.nmf" + type="application/x-nacl" /> + </div> + +</p> + +<p>If the module is working correctly, a click on the "Call fortyTwo()" button + should open a popup dialog containing <b>42</b> as its value.</p> + +<p> Clicking on the "Call reverseText()" button + should open a popup dialog containing the textbox contents and its reverse + as its value.</p> + +<h2>Status</h2> +<div id="statusField">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html b/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html new file mode 100644 index 0000000..c93fb79 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Hello, World!</title> + + <script type="text/javascript"> + helloWorldModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + helloWorldModule = document.getElementById('hello_world'); + updateStatus('SUCCESS'); + } + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + // Set the focus on the text input box. Doing this means you can press + // return as soon as the page loads, and it will fire the reversetText() + // function. + document.forms.helloForm.inputBox.focus(); + if (helloWorldModule == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + function fortyTwo() { + helloWorldModule.postMessage('fortyTwo'); + } + + function reverseText() { + // Grab the text from the text box, pass it into reverseText() + var inputBox = document.forms.helloForm.inputBox; + helloWorldModule.postMessage('reverseText:' + inputBox.value); + // Note: a |false| return tells the <form> tag to cancel the GET action + // when submitting the form. + return false; + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Simple Module</h1> +<p> + <form name="helloForm" + action="" + method="get" + onsubmit="return reverseText()"> + <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p> + <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" /> + <input type="submit" value="Call reverseText()" /> + </form> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="hello_world" + width=0 height=0 + src="hello_world_c_dbg.nmf" + type="application/x-nacl" /> + </div> + +</p> + +<p>If the module is working correctly, a click on the "Call fortyTwo()" button + should open a popup dialog containing <b>42</b> as its value.</p> + +<p> Clicking on the "Call reverseText()" button + should open a popup dialog containing the textbox contents and its reverse + as its value.</p> + +<h2>Status</h2> +<div id="statusField">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/httpd.cmd b/native_client_sdk/src/examples/httpd.cmd new file mode 100644 index 0000000..7c64624 --- /dev/null +++ b/native_client_sdk/src/examples/httpd.cmd @@ -0,0 +1,8 @@ +@echo off
+setlocal
+
+PATH=%CYGWIN%;%PATH%
+REM Use the path to this file (httpd.cmd) to get the
+REM path to httpd.py, so that we can run httpd.cmd from
+REM any directory. Pass up to 9 arguments to httpd.py.
+python %~dp0\httpd.py %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/native_client_sdk/src/examples/httpd.py b/native_client_sdk/src/examples/httpd.py new file mode 100755 index 0000000..3fa8b22 --- /dev/null +++ b/native_client_sdk/src/examples/httpd.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# + +"""A tiny web server. + +This is intended to be used for testing, and only run from within the examples +directory. +""" + +import BaseHTTPServer +import logging +import optparse +import os +import SimpleHTTPServer +import SocketServer +import sys +import urlparse + +logging.getLogger().setLevel(logging.INFO) + +# Using 'localhost' means that we only accept connections +# via the loop back interface. +SERVER_PORT = 5103 +SERVER_HOST = '' + +# We only run from the examples or staging directory so +# that not too much is exposed via this HTTP server. Everything in the +# directory is served, so there should never be anything potentially sensitive +# in the serving directory, especially if the machine might be a +# multi-user machine and not all users are trusted. We only serve via +# the loopback interface. + +SAFE_DIR_COMPONENTS = ['staging', 'examples'] + +def SanityCheckDirectory(): + if os.path.basename(os.getcwd()) in SAFE_DIR_COMPONENTS: + return + logging.error('For security, httpd.py should only be run from one of the') + logging.error('following directories: %s' % SAFE_DIR_COMPONENTS) + logging.error('We are currently in %s', os.getcwd()) + sys.exit(1) + + +# An HTTP server that will quit when |is_running| is set to False. We also use +# SocketServer.ThreadingMixIn in order to handle requests asynchronously for +# faster responses. +class QuittableHTTPServer(SocketServer.ThreadingMixIn, + BaseHTTPServer.HTTPServer): + def serve_forever(self, timeout=0.5): + self.is_running = True + self.timeout = timeout + while self.is_running: + self.handle_request() + + def shutdown(self): + self.is_running = False + return 1 + + +# "Safely" split a string at |sep| into a [key, value] pair. If |sep| does not +# exist in |str|, then the entire |str| is the key and the value is set to an +# empty string. +def KeyValuePair(str, sep='='): + if sep in str: + return str.split(sep) + else: + return [str, ''] + + +# A small handler that looks for '?quit=1' query in the path and shuts itself +# down if it finds that parameter. +class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def do_GET(self): + (_, _, _, query, _) = urlparse.urlsplit(self.path) + url_params = dict([KeyValuePair(key_value) + for key_value in query.split('&')]) + if 'quit' in url_params and '1' in url_params['quit']: + self.send_response(200, 'OK') + self.send_header('Content-type', 'text/html') + self.send_header('Content-length', '0') + self.end_headers() + self.server.shutdown() + return + + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + + +def Run(server_address, + server_class=QuittableHTTPServer, + handler_class=QuittableHTTPHandler): + httpd = server_class(server_address, handler_class) + logging.info("Starting local server on port %d", server_address[1]) + logging.info("To shut down send http://localhost:%d?quit=1", + server_address[1]) + try: + httpd.serve_forever() + except KeyboardInterrupt: + logging.info("Received keyboard interrupt.") + httpd.server_close() + + logging.info("Shutting down local server on port %d", server_address[1]) + + +if __name__ == '__main__': + usage_str = "usage: %prog [options] [optional_portnum]" + parser = optparse.OptionParser(usage=usage_str) + parser.add_option( + '--no_dir_check', dest='do_safe_check', + action='store_false', default=True, + help='Do not ensure that httpd.py is being run from a safe directory.') + (options, args) = parser.parse_args(sys.argv) + if options.do_safe_check: + SanityCheckDirectory() + if len(args) > 2: + print 'Too many arguments specified.' + parser.print_help() + elif len(args) == 2: + Run((SERVER_HOST, int(args[1]))) + else: + Run((SERVER_HOST, SERVER_PORT)) + sys.exit(0) diff --git a/native_client_sdk/src/examples/index.html b/native_client_sdk/src/examples/index.html new file mode 100644 index 0000000..3d72c28 --- /dev/null +++ b/native_client_sdk/src/examples/index.html @@ -0,0 +1,51 @@ +<!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<style type="text/css"> +dt { + font-weight: bold; +} +dd { + margin-bottom: 12pt; + width: 600px; +} +</style> +<link href="http://code.google.com/css/codesite.css" rel="stylesheet" + type="text/css" /> +<title>Native Client Examples</title> +</head> +<body> +<h2>Native Client Examples</h2> +<p>The examples are no longer pre-built in the SDK. To try out the Native +Client examples right now in your Chrome web browser, please see the +<a href="http://www.gonacl.com/dev/sdk.html">SDK page on GoNaCl.com</a> and +download the SDK examples from the +<a href="https://chrome.google.com/webstore/">Chrome Web Store</a>.</p> +<p>If you would like to build and run the examples within the SDK +then run these commands, starting from the examples directory:</p><br /> +<strong>Windows</strong> +<blockquote><code> +cd %NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM%\examples<br /> +scons<br /> +cd %NACL_SDK_ROOT%\staging<br /> +httpd<br /> +</code></blockquote> +<strong>Mac/Linux</strong> +<blockquote><code> +cd $NACL_SDK_ROOT/$NACL_TARGET_PLATFORM/examples<br /> +./scons<br /> +cd $NACL_SDK_ROOT/staging<br /> +./httpd.py<br /> +</code></blockquote> +<p>Happy hacking!</p> +</body> +</html> diff --git a/native_client_sdk/src/examples/index_staging.html b/native_client_sdk/src/examples/index_staging.html new file mode 100644 index 0000000..c90ec1a --- /dev/null +++ b/native_client_sdk/src/examples/index_staging.html @@ -0,0 +1,113 @@ +<!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<style type="text/css"> +dt { + font-weight: bold; +} +dd { + margin-bottom: 12pt; + width: 600px; +} +</style> +<link href="http://code.google.com/css/codesite.css" rel="stylesheet" + type="text/css" /> +<title>Native Client Examples</title> +</head> +<body> +<h2>Native Client Examples</h2> +<p>This page lists all of the examples available in the most recent Native Client SDK bundle. Each example is designed to teach a few specific Native Client programming concepts.</p> +<dl> + <dt><a href="hello_world_c/hello_world_c.html">Hello World in C</a></dt> + <dd>The Hello World In C example demonstrates the basic structure of all Native Client applications. This example loads a Native Client module and responds to button click events by showing alert panels. + + <p>Teaching focus: Basic HTML, JavaScript, and module architecture; Messaging API.</p> + </dd> + <dt><a href="hello_world/hello_world.html">Hello World in C++</a></dt> + <dd>The Hello World C++ example demonstrates the basic structure of all Native Client applications. This example loads a Native Client module and responds to button click events by showing alert panels. + + <p>Teaching focus: Basic HTML, JavaScript, and module architecture; Messaging API.</p> + </dd> +<dt><a href="load_progress/load_progress.html">Load Progress</a></dt> + <dd> The Load Progress example demonstrates how to listen for and handle events that occur while a + NaCl module loads. This example listens for different load event types and dispatches different events to their respective handler. This example also checks for valid browser + version and shows how to calculate and display loading progress. + + <p>Teaching focus: Progress event handling.</p> + </dd> + <dt><a href="pi_generator/pi_generator.html">Pi Generator</a></dt> + <dd> The Pi Generator example demonstrates creating a helper thread that estimate pi using the Monte Carlo + method while randomly putting 1,000,000,000 points inside a 2D square that shares two + sides with a quarter circle. + + <p>Teaching focus: Thread creation, 2D graphics, view change events.</p> + </dd> +<dt><a href="input_events/input_events.html">Input Events</a></dt> + <dd> The Input Events example demonstrates how to handle events triggered by the user. This example allows a user + to interact with a square representing a module instance. Events are displayed on the screen as the user clicks, scrolls, types, inside or outside + of the square. + + <p>Teaching focus: Keyboard and mouse input, view change, and focus events.</p> + </dd> +<dt><a href="sine_synth/sine_synth.html">Sine Wave Synthesizer</a></dt> + <dd> The Sine Wave Synthesizer example demonstrates playing sound (a sine wave). + + <p>Teaching focus: Audio.</p> + </dd> +<dt><a href="pong/pong.html">Pong</a></dt> + <dd> The Pong example demonstrates how to create a basic 2D video game and how to store application + information in a local persistent file. This game uses up and + down arrow keyboard input events to move the paddle. + + <p>Teaching focus: File I/O, 2D graphics, input events.</p> + </dd> + <dt><a href="geturl/geturl.html">Get URL</a></dt> + <dd> The Get URL example demonstrates fetching an URL and then displaying its contents. + + <p>Teaching focus: URL loading.</p> + </dd> + <dt><a href="multithreaded_input_events/mt_input_events.html">Multi-threaded Input Events</a></dt> + <dd>The Multithreaded Input Events example combines HTML, Javascript, + and C++ (the C++ is compiled to create a .nexe file). + The C++ shows how to handle input events in a multi-threaded application. + The main thread converts input events to non-pepper events and puts them on + a queue. The worker thread pulls them off of the queue, converts them to a + string, and then uses CallOnMainThread so that PostMessage can be send the + result of the worker thread to the browser. + </dd> + <dt><a href="tumbler/tumbler.html">Tumbler</a></dt> + <dd> The Tumbler example demonstrates how to create a 3D cube that you can rotate with your mouse while pressing the + left mouse button. This example creates a 3D context and draws to it using + OpenGL ES. The JavaScript implements a virtual trackball interface to + map mouse movements into 3D rotations using simple 3D vector math and + quaternions. + + <p>Teaching focus: 3D graphics</p> + </dd> + <dt><a href="fullscreen_tumbler/fullscreen_tumbler.html">Full-screen Tumbler</a></dt> + <dd> This is a modified version of the Tumbler example above that supports + full-screen display. It is in every way identical to Tumbler in + functionality, except that it adds the ability to switch to/from + full-screen display by pressing the Enter key. + + <p>Teaching focus: Full-screen</p> + </dd> + <dt><a href="mouselock/mouselock.html">Mouse Locker</a></dt> + <dd> The Mouselock example demonstrates how to use the MouseLock API to hide + the mouse cursor. Mouse lock is only available in full-screen mode. You can + lock and unlock the mouse while in full-screen mode by pressing the Enter key. + + <p>Teaching focus: Mouse lock, Full-screen</p> + </dd> +</dl> +</body> +</html> diff --git a/native_client_sdk/src/examples/input_events/build.scons b/native_client_sdk/src/examples/input_events/build.scons new file mode 100644 index 0000000..3ccfc5b --- /dev/null +++ b/native_client_sdk/src/examples/input_events/build.scons @@ -0,0 +1,41 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='input_events', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['input_events.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'input_events') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('input_events') + +app_files = [ + 'input_events.html', + 'input_events.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/input_events/input_events.cc b/native_client_sdk/src/examples/input_events/input_events.cc new file mode 100644 index 0000000..8ef2419 --- /dev/null +++ b/native_client_sdk/src/examples/input_events/input_events.cc @@ -0,0 +1,251 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// C headers +#include <cassert> +#include <cstdio> + +// C++ headers +#include <sstream> +#include <string> + +// NaCl +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/point.h" +#include "ppapi/cpp/var.h" + +namespace { +const char* const kDidChangeView = "DidChangeView"; +const char* const kHandleInputEvent = "DidHandleInputEvent"; +const char* const kDidChangeFocus = "DidChangeFocus"; +const char* const kHaveFocus = "HaveFocus"; +const char* const kDontHaveFocus = "DontHaveFocus"; + +// Convert a given modifier to a descriptive string. Note that the actual +// declared type of modifier in each of the event classes is uint32_t, but it is +// expected to be interpreted as a bitfield of 'or'ed PP_InputEvent_Modifier +// values. +std::string ModifierToString(uint32_t modifier) { + std::string s; + if (modifier & PP_INPUTEVENT_MODIFIER_SHIFTKEY) { + s += "shift "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) { + s += "ctrl "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_ALTKEY) { + s += "alt "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_METAKEY) { + s += "meta "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_ISKEYPAD) { + s += "keypad "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT) { + s += "autorepeat "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) { + s += "left-button-down "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) { + s += "middle-button-down "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN) { + s += "right-button-down "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) { + s += "caps-lock "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) { + s += "num-lock "; + } + return s; +} + +std::string MouseButtonToString(PP_InputEvent_MouseButton button) { + switch (button) { + case PP_INPUTEVENT_MOUSEBUTTON_NONE: + return "None"; + case PP_INPUTEVENT_MOUSEBUTTON_LEFT: + return "Left"; + case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: + return "Middle"; + case PP_INPUTEVENT_MOUSEBUTTON_RIGHT: + return "Right"; + default: + std::ostringstream stream; + stream << "Unrecognized (" + << static_cast<int32_t>(button) + << ")"; + return stream.str(); + } +} + +} // namespace + +class EventInstance : public pp::Instance { + public: + explicit EventInstance(PP_Instance instance) + : pp::Instance(instance) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); + } + virtual ~EventInstance() {} + + /// Clicking outside of the instance's bounding box + /// will create a DidChangeFocus event (the NaCl instance is + /// out of focus). Clicking back inside the instance's + /// bounding box will create another DidChangeFocus event + /// (the NaCl instance is back in focus). The default is + /// that the instance is out of focus. + void DidChangeFocus(bool focus) { + PostMessage(pp::Var(kDidChangeFocus)); + if (focus == true) { + PostMessage(pp::Var(kHaveFocus)); + } else { + PostMessage(pp::Var(kDontHaveFocus)); + } + } + + /// Scrolling the mouse wheel causes a DidChangeView event. + void DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + PostMessage(pp::Var(kDidChangeView)); + } + + void GotKeyEvent(const pp::KeyboardInputEvent& key_event, + const std::string& kind) { + std::ostringstream stream; + stream << pp_instance() << ":" + << " Key event:" << kind + << " modifier:" << ModifierToString(key_event.GetModifiers()) + << " key_code:" << key_event.GetKeyCode() + << " time:" << key_event.GetTimeStamp() + << " text:" << key_event.GetCharacterText().DebugString() + << "\n"; + PostMessage(stream.str()); + } + + void GotMouseEvent(const pp::MouseInputEvent& mouse_event, + const std::string& kind) { + std::ostringstream stream; + stream << pp_instance() << ":" + << " Mouse event:" << kind + << " modifier:" << ModifierToString(mouse_event.GetModifiers()) + << " button:" << MouseButtonToString(mouse_event.GetButton()) + << " x:" << mouse_event.GetPosition().x() + << " y:" << mouse_event.GetPosition().y() + << " click_count:" << mouse_event.GetClickCount() + << " time:" << mouse_event.GetTimeStamp() + << "\n"; + PostMessage(stream.str()); + } + + void GotWheelEvent(const pp::WheelInputEvent& wheel_event) { + std::ostringstream stream; + stream << pp_instance() << ": Wheel event." + << " modifier:" << ModifierToString(wheel_event.GetModifiers()) + << " deltax:" << wheel_event.GetDelta().x() + << " deltay:" << wheel_event.GetDelta().y() + << " wheel_ticks_x:" << wheel_event.GetTicks().x() + << " wheel_ticks_y:"<< wheel_event.GetTicks().y() + << " scroll_by_page: " + << (wheel_event.GetScrollByPage() ? "true" : "false") + << "\n"; + PostMessage(stream.str()); + } + + // Handle an incoming input event by switching on type and dispatching + // to the appropriate subtype handler. + // + // HandleInputEvent operates on the main Pepper thread. In large + // real-world applications, you'll want to create a separate thread + // that puts events in a queue and handles them independant of the main + // thread so as not to slow down the browser. There is an additional + // version of this example in the examples directory that demonstrates + // this best practice. + virtual bool HandleInputEvent(const pp::InputEvent& event) { + PostMessage(pp::Var(kHandleInputEvent)); + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_UNDEFINED: + break; + case PP_INPUTEVENT_TYPE_MOUSEDOWN: + GotMouseEvent(pp::MouseInputEvent(event), "Down"); + break; + case PP_INPUTEVENT_TYPE_MOUSEUP: + GotMouseEvent(pp::MouseInputEvent(event), "Up"); + break; + case PP_INPUTEVENT_TYPE_MOUSEMOVE: + GotMouseEvent(pp::MouseInputEvent(event), "Move"); + break; + case PP_INPUTEVENT_TYPE_MOUSEENTER: + GotMouseEvent(pp::MouseInputEvent(event), "Enter"); + break; + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + GotMouseEvent(pp::MouseInputEvent(event), "Leave"); + break; + case PP_INPUTEVENT_TYPE_WHEEL: + GotWheelEvent(pp::WheelInputEvent(event)); + break; + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + GotKeyEvent(pp::KeyboardInputEvent(event), "RawKeyDown"); + break; + case PP_INPUTEVENT_TYPE_KEYDOWN: + GotKeyEvent(pp::KeyboardInputEvent(event), "Down"); + break; + case PP_INPUTEVENT_TYPE_KEYUP: + GotKeyEvent(pp::KeyboardInputEvent(event), "Up"); + break; + case PP_INPUTEVENT_TYPE_CHAR: + GotKeyEvent(pp::KeyboardInputEvent(event), "Character"); + break; + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + GotKeyEvent(pp::KeyboardInputEvent(event), "Context"); + break; + // Note that if we receive an IME event we just send a message back + // to the browser to indicate we have received it. + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_START")); + break; + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE")); + break; + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_END")); + break; + case PP_INPUTEVENT_TYPE_IME_TEXT: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_TEXT")); + break; + default: + assert(false); + return false; + } + return true; + } +}; + +// The EventModule provides an implementation of pp::Module that creates +// EventInstance objects when invoked. This is part of the glue code that makes +// our example accessible to ppapi. +class EventModule : public pp::Module { + public: + EventModule() : pp::Module() {} + virtual ~EventModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new EventInstance(instance); + } +}; + +// Implement the required pp::CreateModule function that creates our specific +// kind of Module (in this case, EventModule). This is part of the glue code +// that makes our example accessible to ppapi. +namespace pp { + Module* CreateModule() { + return new EventModule(); + } +} diff --git a/native_client_sdk/src/examples/input_events/input_events.html b/native_client_sdk/src/examples/input_events/input_events.html new file mode 100644 index 0000000..3f1695d --- /dev/null +++ b/native_client_sdk/src/examples/input_events/input_events.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 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. + --> +<head> + <title>Input Events</title> + + <script type="text/javascript"> + var kMaxArraySize = 20; + var messageArray = new Array(); + + function $(id) { + return document.getElementById(id); + } + + function receiveMessage(message) { + // Show last |kMaxArraySize| events in html. + messageArray.push(message.data); + if (messageArray.length > kMaxArraySize) { + messageArray.shift(); + } + var newData = messageArray.join('<BR>'); + document.getElementById('eventString').innerHTML = newData; + // Print event to console. + console.log(message.data); + } + </script> +</head> +<body> +<h1>InputEvent Handling Example</h1> + <div id="listener"> + <script type="text/javascript"> + $('listener').addEventListener('message', receiveMessage, true); + </script> + + <embed name="nacl_module" + id="event_module" + width=400 height=400 + src="input_events.nmf" + type="application/x-nacl" + style="background-color:gray" /> + </div> +<p> +This example demonstrates handling of input events in PPAPI.</p> +<p> +Each time an input event happens in the context of the gray box, +the embedded NaCl module posts a message describing the event +back to JavaScript, which prints a message to the JavaScript +console in Chrome and to a string on the page.</p> +<h2>Events</h2> +<pre> +<p><b id='eventString'>None</b></p> +</pre> +</body> +</html> diff --git a/native_client_sdk/src/examples/load_progress/build.scons b/native_client_sdk/src/examples/load_progress/build.scons new file mode 100644 index 0000000..1c8a6a6 --- /dev/null +++ b/native_client_sdk/src/examples/load_progress/build.scons @@ -0,0 +1,51 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='load_progress', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + CCFLAGS=['-pedantic', '-Werror'], + ) + +sources = ['load_progress.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'load_progress') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('load_progress') + +common_files = [ + 'check_browser.js', + ] +common_files = [ + os.path.join(os.path.dirname(os.getcwd()), 'common', common_file) + for common_file in common_files] + +app_files = [ + 'load_progress.html', + 'load_progress.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']), + 'common') +install_common = nacl_env.Install(dir=common_dir, source=common_files) +nacl_env.Alias('install', + source=[install_app, install_common, install_nexes]) diff --git a/native_client_sdk/src/examples/load_progress/load_progress.cc b/native_client_sdk/src/examples/load_progress/load_progress.cc new file mode 100644 index 0000000..07ce440 --- /dev/null +++ b/native_client_sdk/src/examples/load_progress/load_progress.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// @file +/// This example demonstrates loading and running a very simple NaCl +/// module. To load the NaCl module, the browser first looks for the +/// CreateModule() factory method (at the end of this file). It calls +/// CreateModule() once to load the module code from your .nexe. After the +/// .nexe code is loaded, CreateModule() is not called again. +/// +/// Once the .nexe code is loaded, the browser then calls the +/// LoadProgressModule::CreateInstance() +/// method on the object returned by CreateModule(). It calls CreateInstance() +/// each time it encounters an <embed> tag that references your NaCl module. + +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace load_progress { +/// The Instance class. One of these exists for each instance of your NaCl +/// module on the web page. The browser will ask the Module object to create +/// a new Instance for each occurrence of the <embed> tag that has these +/// attributes: +/// <pre> +/// type="application/x-nacl" +/// nacl="hello_world.nmf" +/// </pre> +class LoadProgressInstance : public pp::Instance { + public: + explicit LoadProgressInstance(PP_Instance instance) + : pp::Instance(instance) {} + virtual ~LoadProgressInstance() {} +}; + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with +/// <code>type="application/x-nacl"</code>. +class LoadProgressModule : public pp::Module { + public: + LoadProgressModule() : pp::Module() {} + virtual ~LoadProgressModule() {} + + /// Create and return a HelloWorldInstance object. + /// @param[in] instance a handle to a plug-in instance. + /// @return a newly created HelloWorldInstance. + /// @note The browser is responsible for calling @a delete when done. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new LoadProgressInstance(instance); + } +}; +} // namespace load_progress + + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +/// @return new LoadProgressModule. +/// @note The browser is responsible for deleting returned @a Module. +Module* CreateModule() { + return new load_progress::LoadProgressModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/load_progress/load_progress.html b/native_client_sdk/src/examples/load_progress/load_progress.html new file mode 100644 index 0000000..4c2dc47 --- /dev/null +++ b/native_client_sdk/src/examples/load_progress/load_progress.html @@ -0,0 +1,232 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Load Progress Example</title> + <script type="text/javascript" src="../common/check_browser.js"></script> + <script type="text/javascript"> + // Check for Native Client support in the browser before the DOM loads. + var isValidBrowser = false; + var browserSupportStatus = 0; + var checker = new browser_version.BrowserChecker( + 14, // Minumum Chrome version. + navigator["appVersion"], + navigator["plugins"]); + checker.checkBrowser(); + + loadProgressModule = null; // Global application object. + statusText = 'NO-STATUS'; + + isValidBrowser = checker.getIsValidBrowser(); + browserSupportStatus = checker.getBrowserSupportStatus(); + + // Handler that gets called when the NaCl module starts loading. This + // event is always triggered when an <EMBED> tag has a MIME type of + // application/x-nacl. + function moduleDidStartLoad() { + appendToEventLog('loadstart'); + } + + // Progress event handler. |event| contains a couple of interesting + // properties that are used in this example: + // total The size of the NaCl module in bytes. Note that this value + // is 0 until |lengthComputable| is true. In particular, this + // value is 0 for the first 'progress' event. + // loaded The number of bytes loaded so far. + // lengthComputable A boolean indicating that the |total| field + // represents a valid length. + // + // event The ProgressEvent that triggered this handler. + function moduleLoadProgress(event) { + var loadPercent = 0.0; + var loadPercentString; + if (event.lengthComputable && event.total > 0) { + loadPercent = event.loaded / event.total * 100.0; + loadPercentString = loadPercent + '%'; + } else { + // The total length is not yet known. + loadPercent = -1.0; + loadPercentString = 'Computing...'; + } + appendToEventLog('progress: ' + loadPercentString + + ' (' + event.loaded + ' of ' + event.total + ' bytes)'); + } + + // Handler that gets called if an error occurred while loading the NaCl + // module. Note that the event does not carry any meaningful data about + // the error, you have to check lastError on the <EMBED> element to find + // out what happened. + function moduleLoadError() { + appendToEventLog('error: ' + loadProgressModule.lastError); + } + + // Handler that gets called if the NaCl module load is aborted. + function moduleLoadAbort() { + appendToEventLog('abort'); + } + + // When the NaCl module has loaded indicate success. + function moduleDidLoad() { + loadProgressModule = document.getElementById('load_progress'); + appendToEventLog('load'); + updateStatus('SUCCESS'); + } + + // Handler that gets called when the NaCl module loading has completed. + // You will always get one of these events, regardless of whether the NaCl + // module loaded successfully or not. For example, if there is an error + // during load, you will get an 'error' event and a 'loadend' event. Note + // that if the NaCl module loads successfully, you will get both a 'load' + // event and a 'loadend' event. + function moduleDidEndLoad() { + appendToEventLog('loadend'); + var lastError = event.target.lastError; + if (lastError == undefined || lastError.length == 0) { + lastError = '<none>'; + } + appendToEventLog('lastError: ' + lastError); + } + + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // Set the global status message. Updates the 'status_field' element with + // the new text. + // opt_message The message text. If this is null or undefined, then + // attempt to set the element with id 'status_field' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('status_field'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + + // Append an event name to the 'event_log_field' element. Event names + // are separated by a <br> tag so they get listed one per line. + // logMessage The message to append to the log. + function appendToEventLog(logMessage) { + var eventLogField = document.getElementById('event_log_field'); + if (eventLogField.innerHTML.length == 0) { + eventLogField.innerHTML = logMessage; + } else { + eventLogField.innerHTML = eventLogField.innerHTML + + '<br />' + + logMessage; + } + } + </script> +</head> +<body> + +<h1>Native Client Load Event Example</h1> + +<h2>Event Log</h2> +<div id="event_log_field"></div> +<h2>Status</h2> +<div id="status_field">NO-STATUS</div> + +<div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener'); + listener.addEventListener('loadstart', moduleDidStartLoad, true); + listener.addEventListener('progress', moduleLoadProgress, true); + listener.addEventListener('error', moduleLoadError, true); + listener.addEventListener('abort', moduleLoadAbort, true); + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('loadend', moduleDidEndLoad, true); + listener.addEventListener('message', handleMessage, true); + + switch (browserSupportStatus) { + case browser_version.BrowserChecker.StatusValues.NACL_ENABLED: + appendToEventLog('Native Client plugin enabled.'); + break; + case browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER: + updateStatus('UNKNOWN BROWSER'); + break; + case browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD: + appendToEventLog( + 'Chrome too old: You must use Chrome version 14 or later.'); + updateStatus('NEED CHROME 14 OR LATER'); + break; + case browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED: + appendToEventLog( + 'NaCl disabled: Native Client is not enabled.<br>' + + 'Please go to <b>chrome://plugins</b> and enable Native Client ' + + 'plugin.'); + updateStatus('NaCl NOT ENABLED'); + break; + case browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER: + appendToEventLog( + 'file: URL detected, please use a web server to host Native ' + + 'Client applications.'); + updateStatus('NaCl NOT ENABLED'); + default: + appendToEventLog('Unknown error: Unable to detect browser and/or ' + + 'Native Client support.'); + updateStatus('UNKNOWN ERROR'); + break; + } + </script> + + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <embed name="nacl_module" + id="load_progress" + width=0 height=0 + src="load_progress.nmf" + type="application/x-nacl" /> + + <script type="text/javascript"> + loadProgressModule = document.getElementById('load_progress'); + // Futher diagnose NaCl loading. + if (loadProgressModule == null || + typeof loadProgressModule.readyState == 'undefined') { + switch (browserSupportStatus) { + case browser_version.BrowserChecker.StatusValues.NACL_ENABLED: + // The NaCl plugin is enabled and running, it's likely that the flag + // isn't set. + appendToEventLog( + 'NaCl flag disabled: The Native Client flag is not enabled.<br>' + + 'Please go to <b>chrome://flags</b> enable Native Client and ' + + 'relaunch your browser. See also: ' + + '<a href="http://code.google.com/chrome/nativeclient/docs/' + + 'running.html">Running Web Applications that Use Native Client' + + '</a>'); + updateStatus('NaCl NOT ENABLED'); + break; + case browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER: + appendToEventLog('Native Client applications are not supported by ' + + 'this browser.'); + break; + default: + appendToEventLog('Unknown error when loading Native Client ' + + 'application.'); + } + } + </script> +</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/mouselock/build.scons b/native_client_sdk/src/examples/mouselock/build.scons new file mode 100644 index 0000000..f56186e --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/build.scons @@ -0,0 +1,51 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='mouselock', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + CCFLAGS=['-pedantic', '-Werror'], + ) + +sources = ['mouselock.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'mouselock') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('mouselock') + +common_files = [ + 'check_browser.js', + ] +common_files = [ + os.path.join(os.path.dirname(os.getcwd()), 'common', common_file) + for common_file in common_files] + +app_files = [ + 'mouselock.html', + 'mouselock.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']), + 'common') +install_common = nacl_env.Install(dir=common_dir, source=common_files) +nacl_env.Alias('install', + source=[install_app, install_common, install_nexes]) diff --git a/native_client_sdk/src/examples/mouselock/mouselock.cc b/native_client_sdk/src/examples/mouselock/mouselock.cc new file mode 100644 index 0000000..b0e18fd --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/mouselock.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2011 The Native Client 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 "examples/mouselock/mouselock.h" + +#include <cmath> +#include <cstdlib> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +// Indicate the direction of the mouse location relative to the center of the +// view. These values are used to determine which 2D quadrant the needle lies +// in. +typedef enum { + kLeft = 0, + kRight = 1, + kUp = 2, + kDown = 3 +} MouseDirection; + +namespace { +const int kCentralSpotRadius = 5; +const uint32_t kReturnKeyCode = 13; +const uint32_t kBackgroundColor = 0xff606060; +const uint32_t kLockedForegroundColor = 0xfff08080; +const uint32_t kUnlockedForegroundColor = 0xff80f080; +} // namespace + +namespace mouselock { + +MouseLockInstance::~MouseLockInstance() { + free(background_scanline_); + background_scanline_ = NULL; +} + +bool MouseLockInstance::Init(uint32_t argc, + const char* argn[], + const char* argv[]) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | + PP_INPUTEVENT_CLASS_KEYBOARD); + return true; +} + +bool MouseLockInstance::HandleInputEvent(const pp::InputEvent& event) { + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_MOUSEDOWN: { + is_context_bound_ = false; + if (fullscreen_.IsFullscreen()) { + // Leaving fullscreen mode also unlocks the mouse if it was locked. + // In this case, the browser will call MouseLockLost() on this + // instance. + if (!fullscreen_.SetFullscreen(false)) { + Log("Could not leave fullscreen mode\n"); + } + } else { + if (!fullscreen_.SetFullscreen(true)) { + Log("Could not set fullscreen mode\n"); + } else { + pp::MouseInputEvent mouse_event(event); + if (mouse_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT && + !mouse_locked_) { + LockMouse(callback_factory_.NewRequiredCallback( + &MouseLockInstance::DidLockMouse)); + } + } + } + return true; + } + + case PP_INPUTEVENT_TYPE_MOUSEMOVE: { + pp::MouseInputEvent mouse_event(event); + mouse_movement_ = mouse_event.GetMovement(); + Paint(); + return true; + } + + case PP_INPUTEVENT_TYPE_KEYDOWN: { + pp::KeyboardInputEvent key_event(event); + // Lock the mouse when the Enter key is pressed. + if (key_event.GetKeyCode() == kReturnKeyCode) { + if (mouse_locked_) { + UnlockMouse(); + } else { + LockMouse(callback_factory_.NewRequiredCallback( + &MouseLockInstance::DidLockMouse)); + } + } + return true; + } + + case PP_INPUTEVENT_TYPE_MOUSEUP: + case PP_INPUTEVENT_TYPE_MOUSEENTER: + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + case PP_INPUTEVENT_TYPE_WHEEL: + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + case PP_INPUTEVENT_TYPE_KEYUP: + case PP_INPUTEVENT_TYPE_CHAR: + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + case PP_INPUTEVENT_TYPE_IME_TEXT: + case PP_INPUTEVENT_TYPE_UNDEFINED: + default: + return false; + } +} + +void MouseLockInstance::DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + int width = position.size().width(); + int height = position.size().height(); + + // When entering into full-screen mode, DidChangeView() gets called twice. + // The first time, any 2D context will fail to bind to this pp::Instacne. + if (width == width_ && height == height_ && is_context_bound_) { + return; + } + width_ = width; + height_ = height; + + device_context_ = pp::Graphics2D(this, pp::Size(width_, height_), false); + waiting_for_flush_completion_ = false; + free(background_scanline_); + background_scanline_ = NULL; + is_context_bound_ = BindGraphics(device_context_); + if (!is_context_bound_) { + Log("Could not bind to 2D context\n"); + return; + } + background_scanline_ = static_cast<uint32_t*>( + malloc(width_ * sizeof(*background_scanline_))); + uint32_t* bg_pixel = background_scanline_; + for (int x = 0; x < width_; ++x) { + *bg_pixel++ = kBackgroundColor; + } + Paint(); +} + +void MouseLockInstance::MouseLockLost() { + if (mouse_locked_) { + mouse_locked_ = false; + Paint(); + } else { + PP_NOTREACHED(); + } +} + +void MouseLockInstance::DidLockMouse(int32_t result) { + mouse_locked_ = result == PP_OK; + mouse_movement_.set_x(0); + mouse_movement_.set_y(0); + Paint(); +} + +void MouseLockInstance::DidFlush(int32_t result) { + waiting_for_flush_completion_ = false; +} + +void MouseLockInstance::Paint() { + if (waiting_for_flush_completion_) { + return; + } + pp::ImageData image = PaintImage(width_, height_); + if (image.is_null()) { + Log("Could not create image data\n"); + return; + } + device_context_.ReplaceContents(&image); + waiting_for_flush_completion_ = true; + device_context_.Flush( + callback_factory_.NewRequiredCallback(&MouseLockInstance::DidFlush)); +} + +pp::ImageData MouseLockInstance::PaintImage(int width, int height) { + pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, + pp::Size(width, height), false); + if (image.is_null() || image.data() == NULL) + return image; + + ClearToBackground(&image); + uint32_t foreground_color = mouse_locked_ ? kLockedForegroundColor : + kUnlockedForegroundColor; + DrawCenterSpot(&image, foreground_color); + DrawNeedle(&image, foreground_color); + return image; +} + +void MouseLockInstance::ClearToBackground(pp::ImageData* image) { + if (image == NULL) { + Log("ClearToBackground with NULL image"); + return; + } + if (background_scanline_ == NULL) + return; + int image_height = image->size().height(); + int image_width = image->size().width(); + for (int y = 0; y < image_height; ++y) { + uint32_t* scanline = image->GetAddr32(pp::Point(0, y)); + memcpy(scanline, + background_scanline_, + image_width * sizeof(*background_scanline_)); + } +} + +void MouseLockInstance::DrawCenterSpot(pp::ImageData* image, + uint32_t spot_color) { + if (image == NULL) { + Log("DrawCenterSpot with NULL image"); + return; + } + // Draw the center spot. The ROI is bounded by the size of the spot, plus + // one pixel. + int center_x = image->size().width() / 2; + int center_y = image->size().height() / 2; + int region_of_interest_radius = kCentralSpotRadius + 1; + + for (int y = center_y - region_of_interest_radius; + y < center_y + region_of_interest_radius; + ++y) { + for (int x = center_x - region_of_interest_radius; + x < center_x + region_of_interest_radius; + ++x) { + if (GetDistance(x, y, center_x, center_y) < kCentralSpotRadius) { + *image->GetAddr32(pp::Point(x, y)) = spot_color; + } + } + } +} + +void MouseLockInstance::DrawNeedle(pp::ImageData* image, + uint32_t needle_color) { + if (image == NULL) { + Log("DrawNeedle with NULL image"); + return; + } + if (GetDistance(mouse_movement_.x(), mouse_movement_.y(), 0, 0) <= + kCentralSpotRadius) { + return; + } + + int abs_mouse_x = std::abs(mouse_movement_.x()); + int abs_mouse_y = std::abs(mouse_movement_.y()); + int center_x = image->size().width() / 2; + int center_y = image->size().height() / 2; + pp::Point vertex(mouse_movement_.x() + center_x, + mouse_movement_.y() + center_y); + pp::Point anchor_1; + pp::Point anchor_2; + MouseDirection direction = kLeft; + + if (abs_mouse_x >= abs_mouse_y) { + anchor_1.set_x(center_x); + anchor_1.set_y(center_y - kCentralSpotRadius); + anchor_2.set_x(center_x); + anchor_2.set_y(center_y + kCentralSpotRadius); + direction = (mouse_movement_.x() < 0) ? kLeft : kRight; + if (direction == kLeft) + anchor_1.swap(anchor_2); + } else { + anchor_1.set_x(center_x + kCentralSpotRadius); + anchor_1.set_y(center_y); + anchor_2.set_x(center_x - kCentralSpotRadius); + anchor_2.set_y(center_y); + direction = (mouse_movement_.y() < 0) ? kUp : kDown; + if (direction == kUp) + anchor_1.swap(anchor_2); + } + + for (int y = center_y - abs_mouse_y; y < center_y + abs_mouse_y; ++y) { + for (int x = center_x - abs_mouse_x; x < center_x + abs_mouse_x; ++x) { + bool within_bound_1 = + ((y - anchor_1.y()) * (vertex.x() - anchor_1.x())) > + ((vertex.y() - anchor_1.y()) * (x - anchor_1.x())); + bool within_bound_2 = + ((y - anchor_2.y()) * (vertex.x() - anchor_2.x())) < + ((vertex.y() - anchor_2.y()) * (x - anchor_2.x())); + bool within_bound_3 = + (direction == kUp && y < center_y) || + (direction == kDown && y > center_y) || + (direction == kLeft && x < center_x) || + (direction == kRight && x > center_x); + + if (within_bound_1 && within_bound_2 && within_bound_3) { + *image->GetAddr32(pp::Point(x, y)) = needle_color; + } + } + } +} + + +void MouseLockInstance::Log(const char* format, ...) { + va_list args; + va_start(args, format); + char buf[512]; + vsnprintf(buf, sizeof(buf) - 1, format, args); + buf[sizeof(buf) - 1] = '\0'; + va_end(args); + + pp::Var value(buf); + PostMessage(value); +} + +} // namespace mouselock + +// This object is the global object representing this plugin library as long +// as it is loaded. +class MouseLockModule : public pp::Module { + public: + MouseLockModule() : pp::Module() {} + virtual ~MouseLockModule() {} + + // Override CreateInstance to create your customized Instance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new mouselock::MouseLockInstance(instance); + } +}; + +namespace pp { + +// Factory function for your specialization of the Module object. +Module* CreateModule() { + return new MouseLockModule(); +} + +} // namespace pp + diff --git a/native_client_sdk/src/examples/mouselock/mouselock.h b/native_client_sdk/src/examples/mouselock/mouselock.h new file mode 100644 index 0000000..88650d6 --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/mouselock.h @@ -0,0 +1,102 @@ +// Copyright (c) 2011 The Native Client 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 <cmath> + +#include "ppapi/c/ppb_fullscreen.h" +#include "ppapi/c/ppb_input_event.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/fullscreen.h" +#include "ppapi/cpp/mouse_lock.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/var.h" + +namespace mouselock { + +class MouseLockInstance : public pp::Instance, public pp::MouseLock { + public: + explicit MouseLockInstance(PP_Instance instance) + : pp::Instance(instance), + pp::MouseLock(this), + width_(0), + height_(0), + mouse_locked_(false), + waiting_for_flush_completion_(false), + callback_factory_(this), + fullscreen_(this), + is_context_bound_(false), + background_scanline_(NULL) { + } + virtual ~MouseLockInstance(); + + // Called by the browser when the NaCl module is loaded and all ready to go. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called by the browser to handle incoming input events. + virtual bool HandleInputEvent(const pp::InputEvent& event); + + // Called whenever the in-browser window changes size. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser when mouselock is lost. This happens when the NaCl + // module exits fullscreen mode. + virtual void MouseLockLost(); + + private: + // Return the Cartesian distance between two points. + double GetDistance(int point_1_x, int point_1_y, + int point_2_x, int point_2_y) { + return sqrt(pow(static_cast<double>(point_1_x - point_2_x), 2) + + pow(static_cast<double>(point_1_y - point_2_y), 2)); + } + + // Called when mouse lock has been acquired. Used as a callback to + // pp::MouseLock.LockMouse(). + void DidLockMouse(int32_t result); + + // Called when the 2D context has been flushed to the browser window. Used + // as a callback to pp::Graphics2D.Flush(). + void DidFlush(int32_t result); + + // Creates a new paint buffer, paints it then flush it to the 2D context. If + // a flush is pending, this does nothing. + void Paint(); + + // Create a new pp::ImageData and paint the graphics that represent the mouse + // movement in it. Return the new pp::ImageData. + pp::ImageData PaintImage(int width, int height); + + // Fill the image with the backgroud color. + void ClearToBackground(pp::ImageData* image); + + // Draw a spot in |spot_color| in the center of the image. The radius of the + // spot is defined by a constant value in mouselock.cc + void DrawCenterSpot(pp::ImageData* image, uint32_t spot_color); + + // Draw the needle when the mouse is outside of the central spot. + void DrawNeedle(pp::ImageData* image, uint32_t needle_color); + + // Print the printf-style format to the "console" via PostMessage. + void Log(const char* format, ...); + + int width_; + int height_; + + bool mouse_locked_; + pp::Point mouse_movement_; + bool waiting_for_flush_completion_; + pp::CompletionCallbackFactory<MouseLockInstance> callback_factory_; + + pp::Fullscreen fullscreen_; + pp::Graphics2D device_context_; + bool is_context_bound_; + uint32_t* background_scanline_; +}; + +} // namespace mouselock diff --git a/native_client_sdk/src/examples/mouselock/mouselock.html b/native_client_sdk/src/examples/mouselock/mouselock.html new file mode 100644 index 0000000..eaa44dc --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/mouselock.html @@ -0,0 +1,108 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> +<!-- +Copyright (c) 2011 The Native Client Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<head> + <meta http-equiv="Pragma" content="no-cache" /> + <meta http-equiv="Expires" content="-1" /> + <script type="text/javascript" src="../common/check_browser.js"></script> + <script> + // Check for Native Client support in the browser before the DOM loads. + var isValidBrowser = false; + var browserSupportStatus = 0; + // Fullscreen and mouselock support is in Chrome version 16. + var CHROME_MINIMUM_VERSION = 16; + + var checker = new browser_version.BrowserChecker( + CHROME_MINIMUM_VERSION, + navigator["appVersion"], + navigator["plugins"]); + checker.checkBrowser(); + + isValidBrowser = checker.getIsValidBrowser(); + browserSupportStatus = checker.getBrowserSupportStatus(); + + function handleMessage(message_event) { + console.log(message_event.data); + } + </script> + <title>Full-screen and Mouse-lock Example</title> +</head> +<body title="This tooltip should not be shown if the mouse is locked."> + <h1>Full-screen and Mouse-lock Example</h1> + <ul> + <li>There are two different kinds of fullscreen mode: "tab fullscreen" and + "browser fullscreen". + <ul> + <li>Tab fullscreen refers to when a tab enters fullscreen mode via the + JS or Pepper fullscreen API.</li> + <li>Browser fullscreen refers to the user putting the browser itself + into fullscreen mode from the UI (e.g., pressing F11).</li> + </ul> + <span style="font-weight:bold"> + NOTE: Mouse lock is only allowed in "tab fullscreen" mode. + </span> + </li> + <li>Lock mouse: + <ul> + <li>left click in the grey box; or</li> + <li>right click in the box to ensure that it is focused and + then press Enter key. (You could verify that the tooltip window is + dismissed properly by this second approach.)</li> + </ul> + </li> + <li>Unlock mouse voluntarily (i.e., NaCl module unlocks mouse): + <ul> + <li>press Enter.</li> + </ul> + </li> + <li>Unlock mouse involuntarily (i.e. Chrome unlocks mouse): + <ul> + <li>lose focus; or</li> + <li>press Esc key; or</li> + <li>exit from the "tab fullscreen" mode.</li> + </ul> + </li> + </ul> + <p>Clicking the mouse inside the grey square takes the NaCl module to/from + combined fullscreen and mouselock mode.</p> + <p>While in fullscreen, pressing Enter will exit/enter mouse lock mode.</p> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + if (browserSupportStatus == + browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD) { + alert('This example will only work on Chrome version ' + + CHROME_MINIMUM_VERSION + + ' or later.'); + } else { + var listener = document.getElementById('listener') + listener.addEventListener('message', handleMessage, true); + // Create two instances of the NaCl module. + listener.innerHTML = '<embed id="mouselock_view" ' + + 'type="application/x-nacl" ' + + 'src="mouselock.nmf" ' + + 'width="300" height="300" />'; + } + </script> + </div> +</body> +</html> diff --git a/native_client_sdk/src/examples/multithreaded_input_events/build.scons b/native_client_sdk/src/examples/multithreaded_input_events/build.scons new file mode 100644 index 0000000..00da894 --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/build.scons @@ -0,0 +1,41 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='multithreaded_input_events', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['mt_input_events.cc', 'custom_events.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'mt_input_events') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('mt_input_events') + +app_files = [ + 'mt_input_events.html', + 'mt_input_events.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc new file mode 100644 index 0000000..b281da5 --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2011 The Native Client 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 <sstream> + +#include "examples/multithreaded_input_events/custom_events.h" + +namespace event_queue { + +// Convert a given modifier to a descriptive string. Note that the actual +// declared type of modifier in each of the event classes is uint32_t, but it is +// expected to be interpreted as a bitfield of 'or'ed PP_InputEvent_Modifier +// values. +std::string ModifierToString(uint32_t modifier) { + std::string s; + if (modifier & kShiftKeyModifier) { + s += "shift "; + } + if (modifier & kControlKeyModifier) { + s += "ctrl "; + } + if (modifier & kAltKeyModifier) { + s += "alt "; + } + if (modifier & kMetaKeyModifer) { + s += "meta "; + } + if (modifier & kKeyPadModifier) { + s += "keypad "; + } + if (modifier & kAutoRepeatModifier) { + s += "autorepeat "; + } + if (modifier & kLeftButtonModifier) { + s += "left-button-down "; + } + if (modifier & kMiddleButtonModifier) { + s += "middle-button-down "; + } + if (modifier & kRightButtonModifier) { + s += "right-button-down "; + } + if (modifier & kCapsLockModifier) { + s += "caps-lock "; + } + if (modifier & kNumLockModifier) { + s += "num-lock "; + } + return s; +} + + +std::string KeyEvent::ToString() const { + std::ostringstream stream; + stream << " Key event:" + << " modifier:" << string_event_modifiers() + << " key_code:" << key_code_ + << " time:" << timestamp_ + << " text:" << text_ + << "\n"; + return stream.str(); +} + +std::string MouseEvent::ToString() const { + std::ostringstream stream; + stream << " Mouse event:" + << " modifier:" << string_event_modifiers() + << " button:" << MouseButtonToString(mouse_button_) + << " x:" << x_position_ + << " y:" << y_position_ + << " click_count:" << click_count_ + << " time:" << timestamp_ + << "\n"; + return stream.str(); +} + +std::string WheelEvent::ToString() const { + std::ostringstream stream; + stream << "Wheel event." + << " modifier:" << string_event_modifiers() + << " deltax:" << delta_x_ + << " deltay:" << delta_y_ + << " wheel_ticks_x:" << ticks_x_ + << " wheel_ticks_y:" << ticks_y_ + << " scroll_by_page: " << scroll_by_page_ + << "\n"; + return stream.str(); +} + +std::string MouseEvent::MouseButtonToString(MouseButton button) const { + switch (button) { + case kNone: + return "None"; + case kLeft: + return "Left"; + case kMiddle: + return "Middle"; + case kRight: + return "Right"; + default: + std::ostringstream stream; + stream << "Unrecognized (" + << static_cast<int32_t>(button) + << ")"; + return stream.str(); + } +} + +} // end namespace + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h new file mode 100644 index 0000000..029fb7a --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CUSTOM_EVENTS_H +#define CUSTOM_EVENTS_H + +#include <stdint.h> +#include <string> +namespace event_queue { + +// These functions and classes are used to define a non-Pepper set of +// events. This is typical of what many developers might do, since it +// would be common to convert a Pepper event into some other more +// application-specific type of event (SDL, Qt, etc.). + +// Constants used for Event::event_modifers_ (which is an int) +// are given below. Use powers of 2 so we can use bitwise AND/OR operators. +const uint32_t kShiftKeyModifier = 1 << 0; +const uint32_t kControlKeyModifier = 1 << 1; +const uint32_t kAltKeyModifier = 1 << 2; +const uint32_t kMetaKeyModifer = 1 << 3; +const uint32_t kKeyPadModifier = 1 << 4; +const uint32_t kAutoRepeatModifier = 1 << 5; +const uint32_t kLeftButtonModifier = 1 << 6; +const uint32_t kMiddleButtonModifier = 1 << 7; +const uint32_t kRightButtonModifier = 1 << 8; +const uint32_t kCapsLockModifier = 1 << 9; +const uint32_t kNumLockModifier = 1 << 10; + +std::string ModifierToString(uint32_t modifier); + +// Abstract base class for an Event -- ToString() is not defined. +// With polymorphism, we can have a collection of Event* and call +// ToString() on each one to be able to display the details of each +// event. +class Event { + public: + // Constructor for the base class. + // |modifiers| is an int that uses bit fields to set specific + // changes, such as the Alt key, specific button, etc. See + // ModifierToString() and constants defined in custom_events.cc. + explicit Event(uint32_t modifiers) + : event_modifiers_(modifiers) {} + uint32_t event_modifiers() const {return event_modifiers_;} + std::string string_event_modifiers() const { + return ModifierToString(event_modifiers_); + } + // Convert the WheelEvent to a string + virtual std::string ToString() const = 0; + virtual ~Event() {} + + private: + uint32_t event_modifiers_; +}; + +// Class for a keyboard event. +class KeyEvent : public Event { + public: + // KeyEvent Constructor. |modifiers| is passed to Event base class. + // |keycode| is the ASCII value, |time| is a timestamp, + // |text| is the value as a string. + KeyEvent(uint32_t modifiers, uint32_t keycode, double time, + std::string text) : + Event(modifiers), key_code_(keycode), + timestamp_(time), text_(text) {} + // Convert the WheelEvent to a string + virtual std::string ToString() const; + + private: + uint32_t key_code_; + double timestamp_; + std::string text_; +}; +class MouseEvent : public Event { + public: + // Specify a mouse button, with kNone available for initialization. + enum MouseButton {kNone, kLeft, kMiddle, kRight}; + + // MouseEvent Constructor. |modifiers| is passed to Event base class. + // |button| specifies which button + // |xpos| and |ypos| give the location, + // |clicks| is how many times this same |xpos|,|ypos| + // has been clicked in a row. |time| is a timestamp, + MouseEvent(uint32_t modifiers, MouseButton button, uint32_t xpos, + uint32_t ypos, uint32_t clicks, double time) + : Event(modifiers), mouse_button_(button), + x_position_(xpos), y_position_(ypos), + click_count_(clicks), timestamp_(time) {} + // Convert the WheelEvent to a string + virtual std::string ToString() const; + + private: + MouseButton mouse_button_; + uint32_t x_position_; + uint32_t y_position_; + uint32_t click_count_; + double timestamp_; + + std::string MouseButtonToString(MouseButton button) const; +}; + + +class WheelEvent : public Event { + public: + // WheelEvent Constructor. |modifiers| is passed to Event base class. + // |xticks| and |yticks| specify number of mouse wheel ticks. + // |scroll_by_page| indicates if we have scrolled past the current + // page. |time| is a timestamp, + WheelEvent(int modifiers, uint32_t dx, uint32_t dy, + uint32_t xticks, uint32_t yticks, bool scroll_by_page, + float time) : + Event(modifiers), delta_x_(dx), delta_y_(dy), + ticks_x_(xticks), ticks_y_(yticks), + scroll_by_page_(scroll_by_page), timestamp_(time) {} + // Convert the WheelEvent to a string + virtual std::string ToString() const; + + private: + uint32_t delta_x_; + uint32_t delta_y_; + uint32_t ticks_x_; + uint32_t ticks_y_; + bool scroll_by_page_; + double timestamp_; +}; + + + +} // end namespace + +#endif // CUSTOM_EVENTS_H + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc new file mode 100644 index 0000000..a392ba2 --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc @@ -0,0 +1,323 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// C headers +#include <cassert> +#include <cstdio> + +// C++ headers +#include <sstream> +#include <string> + +#include "examples/multithreaded_input_events/custom_events.h" +#include "examples/multithreaded_input_events/shared_queue.h" +#include "examples/multithreaded_input_events/thread_safe_ref_count.h" + +// NaCl +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/point.h" +#include "ppapi/cpp/var.h" + +namespace event_queue { +const char* const kDidChangeView = "DidChangeView"; +const char* const kHandleInputEvent = "DidHandleInputEvent"; +const char* const kDidChangeFocus = "DidChangeFocus"; +const char* const kHaveFocus = "HaveFocus"; +const char* const kDontHaveFocus = "DontHaveFocus"; +const char* const kCancelMessage = "CANCEL"; + +// Convert a pepper inputevent modifier value into a +// custom event modifier. +unsigned int ConvertEventModifier(uint32_t pp_modifier) { + unsigned int custom_modifier = 0; + if (pp_modifier & PP_INPUTEVENT_MODIFIER_SHIFTKEY) { + custom_modifier |= kShiftKeyModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) { + custom_modifier |= kControlKeyModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_ALTKEY) { + custom_modifier |= kAltKeyModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_METAKEY) { + custom_modifier |= kMetaKeyModifer; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISKEYPAD) { + custom_modifier |= kKeyPadModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT) { + custom_modifier |= kAutoRepeatModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) { + custom_modifier |= kLeftButtonModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) { + custom_modifier |= kMiddleButtonModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN) { + custom_modifier |= kRightButtonModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) { + custom_modifier |= kCapsLockModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) { + custom_modifier |= kNumLockModifier; + } + return custom_modifier; +} + +class EventInstance : public pp::Instance { + public: + explicit EventInstance(PP_Instance instance) + : pp::Instance(instance), + event_thread_(NULL), + callback_factory_(this) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); + } + + // Not guaranteed to be called in Pepper, but a good idea to cancel the + // queue and signal to workers to die if it is called. + virtual ~EventInstance() { + CancelQueueAndWaitForWorker(); + } + + // Create the 'worker thread'. + bool Init(uint32_t argc, const char* argn[], const char* argv[]) { + pthread_create(&event_thread_, NULL, ProcessEventOnWorkerThread, this); + return true; + } + + /// Clicking outside of the instance's bounding box + /// will create a DidChangeFocus event (the NaCl instance is + /// out of focus). Clicking back inside the instance's + /// bounding box will create another DidChangeFocus event + /// (the NaCl instance is back in focus). The default is + /// that the instance is out of focus. + void DidChangeFocus(bool focus) { + PostMessage(pp::Var(kDidChangeFocus)); + if (focus == true) { + PostMessage(pp::Var(kHaveFocus)); + } else { + PostMessage(pp::Var(kDontHaveFocus)); + } + } + + /// Scrolling the mouse wheel causes a DidChangeView event. + void DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + PostMessage(pp::Var(kDidChangeView)); + } + + /// Called by the browser to handle the postMessage() call in Javascript. + /// Detects which method is being called from the message contents, and + /// calls the appropriate function. Posts the result back to the browser + /// asynchronously. + /// @param[in] var_message The message posted by the browser. The only + /// supported message is |kCancelMessage|. If we receive this, we + /// cancel the shared queue. + virtual void HandleMessage(const pp::Var& var_message) { + std::string message = var_message.AsString(); + if (kCancelMessage == message) { + std::string reply = "Received cancel : only Focus events will be " + "displayed. Worker thread for mouse/wheel/keyboard will exit."; + PostMessage(pp::Var(reply)); + printf("Calling cancel queue\n"); + CancelQueueAndWaitForWorker(); + } + } + + // HandleInputEvent operates on the main Pepper thread. Here we + // illustrate copying the Pepper input event to our own custom event type. + // Since we need to use Pepper API calls to convert it, we must do the + // conversion on the main thread. Once we have converted it to our own + // event type, we push that into a thread-safe queue and quickly return. + // The worker thread can process the custom event and do whatever + // (possibly slow) things it wants to do without making the browser + // become unresponsive. + // We dynamically allocate a sub-class of our custom event (Event) + // so that the queue can contain an Event*. + virtual bool HandleInputEvent(const pp::InputEvent& event) { + Event* event_ptr = NULL; + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + case PP_INPUTEVENT_TYPE_IME_TEXT: + // these cases are not handled...fall through below... + case PP_INPUTEVENT_TYPE_UNDEFINED: + break; + case PP_INPUTEVENT_TYPE_MOUSEDOWN: + case PP_INPUTEVENT_TYPE_MOUSEUP: + case PP_INPUTEVENT_TYPE_MOUSEMOVE: + case PP_INPUTEVENT_TYPE_MOUSEENTER: + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + { + pp::MouseInputEvent mouse_event(event); + PP_InputEvent_MouseButton pp_button = mouse_event.GetButton(); + MouseEvent::MouseButton mouse_button = MouseEvent::kNone; + switch (pp_button) { + case PP_INPUTEVENT_MOUSEBUTTON_NONE: + mouse_button = MouseEvent::kNone; + break; + case PP_INPUTEVENT_MOUSEBUTTON_LEFT: + mouse_button = MouseEvent::kLeft; + break; + case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: + mouse_button = MouseEvent::kMiddle; + break; + case PP_INPUTEVENT_MOUSEBUTTON_RIGHT: + mouse_button = MouseEvent::kRight; + break; + } + event_ptr = new MouseEvent( + ConvertEventModifier(mouse_event.GetModifiers()), + mouse_button, mouse_event.GetPosition().x(), + mouse_event.GetPosition().y(), mouse_event.GetClickCount(), + mouse_event.GetTimeStamp()); + } + break; + case PP_INPUTEVENT_TYPE_WHEEL: + { + pp::WheelInputEvent wheel_event(event); + event_ptr = new WheelEvent( + ConvertEventModifier(wheel_event.GetModifiers()), + wheel_event.GetDelta().x(), wheel_event.GetDelta().y(), + wheel_event.GetTicks().x(), wheel_event.GetTicks().y(), + wheel_event.GetScrollByPage(), wheel_event.GetTimeStamp()); + } + break; + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + case PP_INPUTEVENT_TYPE_KEYDOWN: + case PP_INPUTEVENT_TYPE_KEYUP: + case PP_INPUTEVENT_TYPE_CHAR: + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + { + pp::KeyboardInputEvent key_event(event); + event_ptr = new KeyEvent( + ConvertEventModifier(key_event.GetModifiers()), + key_event.GetKeyCode(), key_event.GetTimeStamp(), + key_event.GetCharacterText().DebugString()); + } + break; + default: + { + // For any unhandled events, send a message to the browser + // so that the user is aware of these and can investigate. + std::stringstream oss; + oss << "Default (unhandled) event, type=" << event.GetType(); + PostMessage(oss.str()); + } + break; + } + event_queue_.Push(event_ptr); + return true; + } + + // Return an event from the thread-safe queue, waiting for a new event + // to occur if the queue is empty. Set |was_queue_cancelled| to indicate + // whether the queue was cancelled. If it was cancelled, then the + // Event* will be NULL. + const Event* GetEventFromQueue(bool *was_queue_cancelled) { + Event* event; + QueueGetResult result = event_queue_.GetItem(&event, kWait); + if (result == kQueueWasCancelled) { + *was_queue_cancelled = true; + return NULL; + } + *was_queue_cancelled = false; + return event; + } + + // This method is called from the worker thread using CallOnMainThread. + // It is not static, and allows PostMessage to be called. + void* PostStringToBrowser(int32_t result, std::string data_to_send) { + PostMessage(pp::Var(data_to_send)); + return 0; + } + + // |ProcessEventOnWorkerThread| is a static method that is run + // by a thread. It pulls events from the queue, converts + // them to a string, and calls CallOnMainThread so that + // PostStringToBrowser will be called, which will call PostMessage + // to send the converted event back to the browser. + static void* ProcessEventOnWorkerThread(void* param) { + EventInstance* event_instance = static_cast<EventInstance*>(param); + while (1) { + // Grab a generic Event* so that down below we can call + // event->ToString(), which will use the correct virtual method + // to convert the event to a string. This 'conversion' is + // the 'work' being done on the worker thread. In an application + // the work might involve changing application state based on + // the event that was processed. + bool queue_cancelled; + const Event* event = event_instance->GetEventFromQueue(&queue_cancelled); + if (queue_cancelled) { + printf("Queue was cancelled, worker thread exiting\n"); + pthread_exit(NULL); + } + std::string event_string = event->ToString(); + delete event; + // Need to invoke callback on main thread. + pp::Module::Get()->core()->CallOnMainThread( + 0, + event_instance->callback_factory().NewCallback( + &EventInstance::PostStringToBrowser, + event_string)); + } // end of while loop. + return 0; + } + + // Return the callback factory. + // Allows the static method (ProcessEventOnWorkerThread) to use + // the |event_instance| pointer to get the factory. + pp::CompletionCallbackFactory<EventInstance, ThreadSafeRefCount>& + callback_factory() { + return callback_factory_; + } + + private: + // Cancels the queue (which will cause the thread to exit). + // Wait for the thread. Set |event_thread_| to NULL so we only + // execute the body once. + void CancelQueueAndWaitForWorker() { + if (event_thread_) { + event_queue_.CancelQueue(); + pthread_join(event_thread_, NULL); + event_thread_ = NULL; + } + } + pthread_t event_thread_; + LockingQueue<Event*> event_queue_; + pp::CompletionCallbackFactory<EventInstance, ThreadSafeRefCount> + callback_factory_; +}; + +// The EventModule provides an implementation of pp::Module that creates +// EventInstance objects when invoked. This is part of the glue code that makes +// our example accessible to ppapi. +class EventModule : public pp::Module { + public: + EventModule() : pp::Module() {} + virtual ~EventModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new EventInstance(instance); + } +}; + +} // namespace + +// Implement the required pp::CreateModule function that creates our specific +// kind of Module (in this case, EventModule). This is part of the glue code +// that makes our example accessible to ppapi. +namespace pp { + Module* CreateModule() { + return new event_queue::EventModule(); + } +} + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html new file mode 100644 index 0000000..7e85e1c --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 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. + --> +<head> + <title>Input Events</title> + + <script type="text/javascript"> + var kMaxArraySize = 20; + var messageArray = new Array(); + var eventModule = null; + + function $(id) { + return document.getElementById(id); + } + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + eventModule = document.getElementById('event_module'); + } + + function receiveMessage(message) { + // Show last |kMaxArraySize| events in html. + messageArray.push(message.data); + if (messageArray.length > kMaxArraySize) { + messageArray.shift(); + } + var newData = messageArray.join('<BR>'); + document.getElementById('eventString').innerHTML = newData; + // Print event to console. + console.log(message.data); + } + function cancelQueue() { + if (eventModule == null) { + console.log('Module is not loaded.'); + return; + } + eventModule.postMessage('CANCEL'); + } + </script> +</head> +<body> +<h1>InputEvent Handling Example</h1> + <div id="listener"> + <script type="text/javascript"> + $('listener').addEventListener('message', receiveMessage, true); + $('listener').addEventListener('load', moduleDidLoad, true); + </script> + <button onclick="cancelQueue()">Kill worker thread and queue</button> + <p/> + <embed name="nacl_module" + id="event_module" + width=400 height=400 + src="mt_input_events.nmf" + type="application/x-nacl" + style="background-color:gray" /> + </div> +<p> +This example demonstrates handling of input events in PPAPI.</p> +<p> +Each time an input event happens in the context of the gray box, +the main thread in the embedded NaCl module converts it from a Pepper input +event to a non-Pepper event and puts this custom event onto a shared queue. +A worker thread in the embedded NaCl module reads events from the queue, +and converts each event to a string and then uses CallOnMainThread to post a +message describing the event back to JavaScript, which prints a message to the +JavaScript console in Chrome and to a string on the page.</p> +<p> +If you press the 'Kill worker thread and queue' button, then the main thread +(which puts events on the queue) will call CancelQueue, indicating that the +main thread will no longer put events on the queue. When the worker sees that +the shared queue has been cancelled, the worker thread will terminate.</p> +<h2>Events</h2> +<pre><p><b id='eventString'>None</b></p> +</pre> +</body> +</html> diff --git a/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h b/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h new file mode 100644 index 0000000..22ff7ae --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h @@ -0,0 +1,137 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHARED_QUEUE_H +#define SHARED_QUEUE_H + +#include <pthread.h> +#include <cassert> +#include <deque> + +#include "examples/multithreaded_input_events/thread_safe_ref_count.h" + +namespace event_queue { + +// This file provides a queue that uses a mutex and condition variable so that +// one thread can put pointers into the queue and another thread can pull items +// out of the queue. + +const int kPthreadMutexSuccess = 0; + +// Specifies whether we want to wait for the queue. +enum QueueWaitingFlag { + kWait = 0, + kDontWait +}; + +// Indicates if we got an item, did not wait, or if the queue was cancelled. +enum QueueGetResult { + kReturnedItem = 0, + kDidNotWait = 1, + kQueueWasCancelled +}; + +// LockingQueue contains a collection of <T>, such as a collection of +// objects or pointers. The Push() method is used to add items to the +// queue in a thread-safe manner. The GetItem() is used to retrieve +// items from the queue in a thread-safe manner. +template <class T> +class LockingQueue { + public: + LockingQueue() : quit_(false) { + int result = pthread_mutex_init(&queue_mutex_, NULL); + assert(result == 0); + result = pthread_cond_init(&queue_condition_var_, NULL); + assert(result == 0); + } + ~LockingQueue() { + pthread_mutex_destroy(&queue_mutex_); + } + + // The producer (who instantiates the queue) calls this to tell the + // consumer that the queue is no longer being used. + void CancelQueue() { + ScopedLock scoped_mutex(&queue_mutex_); + quit_ = true; + // Signal the condition var so that if a thread is waiting in + // GetItem the thread will wake up and see that the queue has + // been cancelled. + pthread_cond_signal(&queue_condition_var_); + } + + // The consumer calls this to see if the queue has been cancelled by + // the producer. If so, the thread should not call GetItem and may + // need to terminate -- i.e. in a case where the producer created + // the consumer thread. + bool IsCancelled() { + ScopedLock scoped_mutex(&queue_mutex_); + return quit_; + } + + // Grabs the mutex and pushes a new item to the end of the queue if the + // queue is not full. Signals the condition variable so that a thread + // that is waiting will wake up and grab the item. + void Push(const T& item) { + ScopedLock scoped_mutex(&queue_mutex_); + the_queue_.push_back(item); + pthread_cond_signal(&queue_condition_var_); + } + + // Tries to pop the front element from the queue; returns an enum: + // kReturnedItem if an item is returned in |item_ptr|, + // kDidNotWait if |wait| was kDontWait and the queue was empty, + // kQueueWasCancelled if the producer called CancelQueue(). + // If |wait| is kWait, GetItem will wait to return until the queue + // contains an item (unless the queue is cancelled). + QueueGetResult GetItem(T* item_ptr, QueueWaitingFlag wait) { + // Because we use both pthread_mutex_lock and pthread_cond_wait, + // we directly use the mutex instead of using ScopedLock. + ScopedLock scoped_mutex(&queue_mutex_); + // Use a while loop to get an item. If the user does not want to wait, + // we will exit from the loop anyway, unlocking the mutex. + // If the user does want to wait, we will wait for pthread_cond_wait, + // and the while loop will check is_empty_no_locking() one more + // time so that a spurious wake-up of pthread_cond_wait is handled. + // If |quit_| has been set, break out of the loop. + while (!quit_ && is_empty_no_locking()) { + // If user doesn't want to wait, return... + if (kDontWait == wait) { + return kDidNotWait; + } + // Wait for signal to occur. + pthread_cond_wait(&queue_condition_var_, &queue_mutex_); + } + // Check to see if quit_ woke us up + if (quit_) { + return kQueueWasCancelled; + } + + // At this point, the queue was either not empty or, if it was empty, + // we called pthread_cond_wait (which released the mutex, waited for the + // signal to occur, and then atomically reacquired the mutex). + // Thus, if we are here, the queue cannot be empty because we either + // had the mutex and verified it was not empty, or we waited for the + // producer to put an item in and signal a single thread (us). + T& item = the_queue_.front(); + *item_ptr = item; + the_queue_.pop_front(); + return kReturnedItem; + } + + private: + std::deque<T> the_queue_; + bool quit_; + pthread_mutex_t queue_mutex_; + pthread_cond_t queue_condition_var_; + + // This is used by methods that already have the lock. + bool is_empty_no_locking() const { + return the_queue_.empty(); + } +}; + +} // end of unnamed namespace + +#endif // SHARED_QUEUE_H + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h b/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h new file mode 100644 index 0000000..f4e6beb --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h @@ -0,0 +1,63 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THREAD_SAFE_REF_COUNT_H +#define THREAD_SAFE_REF_COUNT_H + +#include <pthread.h> +#include <cassert> + +const int kPthreadMutexSuccess = 0; + +namespace event_queue { +// Some synchronization helper classes. + +class ScopedLock { + public: + explicit ScopedLock(pthread_mutex_t* mutex) + : mutex_(mutex) { + if (pthread_mutex_lock(mutex_) != kPthreadMutexSuccess) { + mutex_ = NULL; + } + } + ~ScopedLock() { + if (mutex_ != NULL) { + pthread_mutex_unlock(mutex_); + } + } + private: + ScopedLock& operator=(const ScopedLock&); // Not implemented, do not use. + ScopedLock(const ScopedLock&); // Not implemented, do not use. + pthread_mutex_t* mutex_; // Weak reference, passed in to constructor. +}; + +class ThreadSafeRefCount { + public: + ThreadSafeRefCount() + : ref_(0) { + Init(); + } + + void Init() { + pthread_mutex_init(&mutex_, NULL); + } + + int32_t AddRef() { + ScopedLock s(&mutex_); + return ++ref_; + } + + int32_t Release() { + ScopedLock s(&mutex_); + return --ref_; + } + + private: + int32_t ref_; + pthread_mutex_t mutex_; +}; + +} // namespace +#endif // THREAD_SAFE_REF_COUNT_H + diff --git a/native_client_sdk/src/examples/pi_generator/build.scons b/native_client_sdk/src/examples/pi_generator/build.scons new file mode 100644 index 0000000..8148242 --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='pi_generator', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['pi_generator.cc', 'pi_generator_module.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'pi_generator') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('pi_generator') + +app_files = [ + 'pi_generator.html', + 'pi_generator.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.cc b/native_client_sdk/src/examples/pi_generator/pi_generator.cc new file mode 100644 index 0000000..f5e6e4d --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2011 The Native Client 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 "examples/pi_generator/pi_generator.h" + +#include <stdio.h> +#include <stdlib.h> +#include <cassert> +#include <cmath> +#include <cstring> +#include <string> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/var.h" + +namespace { +const int kPthreadMutexSuccess = 0; +const char* const kPaintMethodId = "paint"; +const double kInvalidPiValue = -1.0; +const int kMaxPointCount = 1000000000; // The total number of points to draw. +const uint32_t kOpaqueColorMask = 0xff000000; // Opaque pixels. +const uint32_t kRedMask = 0xff0000; +const uint32_t kBlueMask = 0xff; +const uint32_t kRedShift = 16; +const uint32_t kBlueShift = 0; + +// This is called by the browser when the 2D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<pi_generator::PiGenerator*>(data)->set_flush_pending(false); +} +} // namespace + +namespace pi_generator { + +// A small helper RAII class that implementes a scoped pthread_mutex lock. +class ScopedMutexLock { + public: + explicit ScopedMutexLock(pthread_mutex_t* mutex) : mutex_(mutex) { + if (pthread_mutex_lock(mutex_) != kPthreadMutexSuccess) { + mutex_ = NULL; + } + } + ~ScopedMutexLock() { + if (mutex_) + pthread_mutex_unlock(mutex_); + } + bool is_valid() const { + return mutex_ != NULL; + } + private: + pthread_mutex_t* mutex_; // Weak reference. +}; + +// A small helper RAII class used to acquire and release the pixel lock. +class ScopedPixelLock { + public: + explicit ScopedPixelLock(PiGenerator* image_owner) + : image_owner_(image_owner), pixels_(image_owner->LockPixels()) {} + + ~ScopedPixelLock() { + pixels_ = NULL; + image_owner_->UnlockPixels(); + } + + uint32_t* pixels() const { + return pixels_; + } + private: + PiGenerator* image_owner_; // Weak reference. + uint32_t* pixels_; // Weak reference. + + ScopedPixelLock(); // Not implemented, do not use. +}; + +PiGenerator::PiGenerator(PP_Instance instance) + : pp::Instance(instance), + graphics_2d_context_(NULL), + pixel_buffer_(NULL), + flush_pending_(false), + quit_(false), + compute_pi_thread_(0), + pi_(0.0) { + pthread_mutex_init(&pixel_buffer_mutex_, NULL); +} + +PiGenerator::~PiGenerator() { + quit_ = true; + if (compute_pi_thread_) { + pthread_join(compute_pi_thread_, NULL); + } + DestroyContext(); + // The ComputePi() thread should be gone by now, so there is no need to + // acquire the mutex for |pixel_buffer_|. + delete pixel_buffer_; + pthread_mutex_destroy(&pixel_buffer_mutex_); +} + +void PiGenerator::DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + if (position.size().width() == width() && + position.size().height() == height()) + return; // Size didn't change, no need to update anything. + + // Create a new device context with the new size. + DestroyContext(); + CreateContext(position.size()); + // Delete the old pixel buffer and create a new one. + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + delete pixel_buffer_; + pixel_buffer_ = NULL; + if (graphics_2d_context_ != NULL) { + pixel_buffer_ = new pp::ImageData(this, + PP_IMAGEDATAFORMAT_BGRA_PREMUL, + graphics_2d_context_->size(), + false); + } +} + +bool PiGenerator::Init(uint32_t argc, const char* argn[], const char* argv[]) { + pthread_create(&compute_pi_thread_, NULL, ComputePi, this); + return true; +} + +uint32_t* PiGenerator::LockPixels() { + void* pixels = NULL; + // Do not use a ScopedMutexLock here, since the lock needs to be held until + // the matching UnlockPixels() call. + if (pthread_mutex_lock(&pixel_buffer_mutex_) == kPthreadMutexSuccess) { + if (pixel_buffer_ != NULL && !pixel_buffer_->is_null()) { + pixels = pixel_buffer_->data(); + } + } + return reinterpret_cast<uint32_t*>(pixels); +} + +void PiGenerator::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + PostMessage(pp::Var(kInvalidPiValue)); + } + std::string message = var_message.AsString(); + if (message == kPaintMethodId) { + Paint(); + } else { + PostMessage(pp::Var(kInvalidPiValue)); + } +} + +void PiGenerator::UnlockPixels() const { + pthread_mutex_unlock(&pixel_buffer_mutex_); +} + +void PiGenerator::Paint() { + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + if (!scoped_mutex.is_valid()) { + return; + } + FlushPixelBuffer(); + // Post the current estimate of Pi back to the browser. + pp::Var pi_estimate(pi()); + // Paint() is called on the main thread, so no need for CallOnMainThread() + // here. It's OK to just post the message. + PostMessage(pi_estimate); +} + +void PiGenerator::CreateContext(const pp::Size& size) { + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + if (!scoped_mutex.is_valid()) { + return; + } + if (IsContextValid()) + return; + graphics_2d_context_ = new pp::Graphics2D(this, size, false); + if (!BindGraphics(*graphics_2d_context_)) { + printf("Couldn't bind the device context\n"); + } +} + +void PiGenerator::DestroyContext() { + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + if (!scoped_mutex.is_valid()) { + return; + } + if (!IsContextValid()) + return; + delete graphics_2d_context_; + graphics_2d_context_ = NULL; +} + +void PiGenerator::FlushPixelBuffer() { + if (!IsContextValid()) + return; + // Note that the pixel lock is held while the buffer is copied into the + // device context and then flushed. + graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point()); + if (flush_pending()) + return; + set_flush_pending(true); + graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this)); +} + +void* PiGenerator::ComputePi(void* param) { + int count = 0; // The number of points put inside the inscribed quadrant. + unsigned int seed = 1; + PiGenerator* pi_generator = static_cast<PiGenerator*>(param); + srand(seed); + for (int i = 1; i <= kMaxPointCount && !pi_generator->quit(); ++i) { + ScopedPixelLock scoped_pixel_lock(pi_generator); + uint32_t* pixel_bits = scoped_pixel_lock.pixels(); + if (pixel_bits == NULL) { + // Note that if the pixel buffer never gets initialized, this won't ever + // paint anything. Which is probably the right thing to do. Also, this + // clause means that the image will not get the very first few Pi dots, + // since it's possible that this thread starts before the pixel buffer is + // initialized. + continue; + } + double x = static_cast<double>(rand_r(&seed)) / RAND_MAX; + double y = static_cast<double>(rand_r(&seed)) / RAND_MAX; + double distance = sqrt(x * x + y * y); + int px = x * pi_generator->width(); + int py = (1.0 - y) * pi_generator->height(); + uint32_t color = pixel_bits[pi_generator->width() * py + px]; + if (distance < 1.0) { + // Set color to blue. + ++count; + pi_generator->pi_ = 4.0 * count / i; + color += 4 << kBlueShift; + color &= kBlueMask; + } else { + // Set color to red. + color += 4 << kRedShift; + color &= kRedMask; + } + pixel_bits[pi_generator->width() * py + px] = color | kOpaqueColorMask; + } + return 0; +} + +} // namespace pi_generator diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.h b/native_client_sdk/src/examples/pi_generator/pi_generator.h new file mode 100644 index 0000000..beecf41 --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator.h @@ -0,0 +1,117 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_ +#define EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_ + +#include <pthread.h> +#include <map> +#include <vector> +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" + +namespace pi_generator { + +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// nacl="pi_generator.nmf" +class PiGenerator : public pp::Instance { + public: + explicit PiGenerator(PP_Instance instance); + virtual ~PiGenerator(); + + // Start up the ComputePi() thread. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Update the graphics context to the new size, and regenerate |pixel_buffer_| + // to fit the new size as well. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser to handle the postMessage() call in Javascript. + // The message in this case is expected to contain the string 'paint', and + // if so this invokes the Paint() function. If |var_message| is not a string + // type, or contains something other than 'paint', this method posts an + // invalid value for Pi (-1.0) back to the browser. + virtual void HandleMessage(const pp::Var& var_message); + + // Return a pointer to the pixels represented by |pixel_buffer_|. When this + // method returns, the underlying |pixel_buffer_| object is locked. This + // call must have a matching UnlockPixels() or various threading errors + // (e.g. deadlock) will occur. + uint32_t* LockPixels(); + // Release the image lock acquired by LockPixels(). + void UnlockPixels() const; + + // Flushes its contents of |pixel_buffer_| to the 2D graphics context. The + // ComputePi() thread fills in |pixel_buffer_| pixels as it computes Pi. + // This method is called by HandleMessage when a message containing 'paint' + // is received. Echos the current value of pi as computed by the Monte Carlo + // method by posting the value back to the browser. + void Paint(); + + bool quit() const { + return quit_; + } + + // |pi_| is computed in the ComputePi() thread. + double pi() const { + return pi_; + } + + int width() const { + return pixel_buffer_ ? pixel_buffer_->size().width() : 0; + } + int height() const { + return pixel_buffer_ ? pixel_buffer_->size().height() : 0; + } + + // Indicate whether a flush is pending. This can only be called from the + // main thread; it is not thread safe. + bool flush_pending() const { + return flush_pending_; + } + void set_flush_pending(bool flag) { + flush_pending_ = flag; + } + + private: + // Create and initialize the 2D context used for drawing. + void CreateContext(const pp::Size& size); + // Destroy the 2D drawing context. + void DestroyContext(); + // Push the pixels to the browser, then attempt to flush the 2D context. If + // there is a pending flush on the 2D context, then update the pixels only + // and do not flush. + void FlushPixelBuffer(); + + bool IsContextValid() const { + return graphics_2d_context_ != NULL; + } + + mutable pthread_mutex_t pixel_buffer_mutex_; + pp::Graphics2D* graphics_2d_context_; + pp::ImageData* pixel_buffer_; + bool flush_pending_; + bool quit_; + pthread_t compute_pi_thread_; + double pi_; + + // ComputePi() estimates Pi using Monte Carlo method and it is executed by a + // separate thread created in SetWindow(). ComputePi() puts kMaxPointCount + // points inside the square whose length of each side is 1.0, and calculates + // the ratio of the number of points put inside the inscribed quadrant divided + // by the total number of random points to get Pi/4. + static void* ComputePi(void* param); +}; + +} // namespace pi_generator + +#endif // EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_ + diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.html b/native_client_sdk/src/examples/pi_generator/pi_generator.html new file mode 100644 index 0000000..f63e73d --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator.html @@ -0,0 +1,83 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Monte Carlo Estimate for Pi</title> + <script type="text/javascript"> + var piGenerator = null; + var paintInterval = null; + + // Start up the paint timer when the NaCl module has loaded. + function moduleDidLoad() { + piGenerator = document.getElementById('piGenerator'); + paintInterval = setInterval('piGenerator.postMessage("paint")', 5); + } + + // Handle a message coming from the NaCl module. The message payload is + // assumed to contain the current estimated value of Pi. Update the Pi + // text display with this value. + function handleMessage(message_event) { + document.form.pi.value = message_event.data; + } + + function pageDidUnload() { + clearInterval(paintInterval); + } + </script> + </head> + <body id="bodyId" onunload="pageDidUnload()"> + <h1>Monte Carlo Estimate for Pi</h1> + <p> + The Native Client module executed in this page creates a thread + that estimates pi (π) using the Monte Carlo method. + The thread randomly puts 1,000,000,000 points + inside a square that shares two sides with a quarter circle (a quadrant). + Because the area of + the quadrant is r²π/4 + and the area of + the square is r², + dividing the number of points inside the quadrant + by the number of points inside the square gives us + an estimate of π/4. + The textbox under the square + shows the current estimate of π. + </p> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the + runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl + module. To load the debug versions of your .nexes, set the 'src' + attribute to the _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="piGenerator" + width=200 height=200 + src="pi_generator.nmf" + type="application/x-nacl" /> + </div> + <br /> + <form name="form"> + <input type="text" size="15" name="pi" /> + </form> + </body> +</html> diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html b/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html new file mode 100644 index 0000000..3308b15 --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html @@ -0,0 +1,83 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>Monte Carlo Estimate for Pi</title>
+ <script type="text/javascript">
+ var piGenerator = null;
+ var paintInterval = null;
+
+ // Start up the paint timer when the NaCl module has loaded.
+ function moduleDidLoad() {
+ piGenerator = document.getElementById('piGenerator');
+ paintInterval = setInterval('piGenerator.postMessage("paint")', 5);
+ }
+
+ // Handle a message coming from the NaCl module. The message payload is
+ // assumed to contain the current estimated value of Pi. Update the Pi
+ // text display with this value.
+ function handleMessage(message_event) {
+ document.form.pi.value = message_event.data;
+ }
+
+ function pageDidUnload() {
+ clearInterval(paintInterval);
+ }
+ </script>
+ </head>
+ <body id="bodyId" onunload="pageDidUnload()">
+ <h1>Monte Carlo Estimate for Pi</h1>
+ <p>
+ The Native Client module executed in this page creates a thread
+ that estimates pi (π) using the Monte Carlo method.
+ The thread randomly puts 1,000,000,000 points
+ inside a square that shares two sides with a quarter circle (a quadrant).
+ Because the area of
+ the quadrant is r²π/4
+ and the area of
+ the square is r²,
+ dividing the number of points inside the quadrant
+ by the number of points inside the square gives us
+ an estimate of π/4.
+ The textbox under the square
+ shows the current estimate of π.
+ </p>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the
+ runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl
+ module. To load the debug versions of your .nexes, set the 'src'
+ attribute to the _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="piGenerator"
+ width=200 height=200
+ src="pi_generator_dbg.nmf"
+ type="application/x-nacl" />
+ </div>
+ <br />
+ <form name="form">
+ <input type="text" size="15" name="pi" />
+ </form>
+ </body>
+</html>
diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc b/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc new file mode 100644 index 0000000..8477fbf --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Native Client 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 <ppapi/cpp/module.h> + +#include "examples/pi_generator/pi_generator.h" + +namespace pi_generator { +// The Module class. The browser calls the CreateInstance() method to create +// an instance of your NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class PiGeneratorModule : public pp::Module { + public: + PiGeneratorModule() : pp::Module() {} + virtual ~PiGeneratorModule() {} + + // Create and return a PiGeneratorInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new PiGenerator(instance); + } +}; +} // namespace pi_generator + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new pi_generator::PiGeneratorModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/pong/build.scons b/native_client_sdk/src/examples/pong/build.scons new file mode 100644 index 0000000..a438bb0 --- /dev/null +++ b/native_client_sdk/src/examples/pong/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='pong', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['pong.cc', 'pong_module.cc', 'view.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'pong') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('pong') + +app_files = [ + 'pong.html', + 'pong.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/pong/pong.cc b/native_client_sdk/src/examples/pong/pong.cc new file mode 100644 index 0000000..75ffc7c --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong.cc @@ -0,0 +1,414 @@ +// Copyright (c) 2011 The Native Client 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 "examples/pong/pong.h" + +#include <stdio.h> +#include <cmath> +#include <string> +#include "examples/pong/view.h" +#include "ppapi/c/pp_file_info.h" +#include "ppapi/c/ppb_file_io.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/file_io.h" +#include "ppapi/cpp/file_ref.h" +#include "ppapi/cpp/file_system.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/var.h" + +namespace { + +const uint32_t kSpaceBar = 0x20; +const uint32_t kUpArrow = 0x26; +const uint32_t kDownArrow = 0x28; + +const int32_t kMaxPointsAllowed = 256; +const std::string kResetScoreMethodId = "resetScore"; +const uint32_t kUpdateDistance = 4; +const int32_t kUpdateInterval = 17; // milliseconds + +} // namespace + +namespace pong { + +// Callbacks that are called asynchronously by the system as a result of various +// pp::FileIO methods. +namespace AsyncCallbacks { +// Callback that is called as a result of pp::FileIO::Flush +void FlushCallback(void*, int32_t) { +} + +// Callback that is called as a result of pp::FileIO::Write +void WriteCallback(void* data, int32_t bytes_written) { + if (bytes_written < 0) + return; // error + Pong* pong = static_cast<Pong*>(data); + pong->offset_ += bytes_written; + if (pong->offset_ == pong->bytes_buffer_.length()) { + pong->file_io_->Flush(pp::CompletionCallback(FlushCallback, NULL)); + } else { + // Not all the bytes to be written have been written, so call + // pp::FileIO::Write again. + pong->file_io_->Write(pong->offset_, &pong->bytes_buffer_[pong->offset_], + pong->bytes_buffer_.length() - pong->offset_, + pp::CompletionCallback(WriteCallback, pong)); + } +} + +// Callback that is called as a result of pp::FileSystem::Open +void FileSystemOpenCallback(void* data, int32_t result) { + if (result != PP_OK) + return; + Pong* pong = static_cast<Pong*>(data); + pong->UpdateScoreFromFile(); +} + +// Callback that is called as a result of pp::FileIO::Read +void ReadCallback(void* data, int32_t bytes_read) { + if (bytes_read < 0) + return; // error + Pong* pong = static_cast<Pong*>(data); + pong->bytes_to_read_ -= bytes_read; + if (pong->bytes_to_read_ == 0) { + // File has been read to completion. Parse the bytes to get the scores. + pong->UpdateScoreFromBuffer(); + } else { + pong->offset_ += bytes_read; + pong->file_io_->Read(pong->offset_, + &pong->bytes_buffer_[pong->offset_], + pong->bytes_to_read_, + pp::CompletionCallback(ReadCallback, pong)); + } +} + +// Callback that is called as a result of pp::FileIO::Query +void QueryCallback(void* data, int32_t result) { + if (result != PP_OK) + return; + Pong* pong = static_cast<Pong*>(data); + pong->bytes_to_read_ = pong->file_info_.size; + pong->offset_ = 0; + pong->bytes_buffer_.resize(pong->bytes_to_read_); + pong->file_io_->Read(pong->offset_, + &pong->bytes_buffer_[0], + pong->bytes_to_read_, + pp::CompletionCallback(ReadCallback, pong)); +} + +// Callback that is called as a result of pp::FileIO::Open +void FileOpenCallback(void*data, int32_t result) { + if (result != PP_OK) { + return; + } + Pong* pong = static_cast<Pong*>(data); + // Query the file in order to get the file size. + pong->file_io_->Query(&pong->file_info_, pp::CompletionCallback(QueryCallback, + pong)); +} + +// Callback that is called as a result of pp::Core::CallOnMainThread +void UpdateCallback(void* data, int32_t /*result*/) { + Pong* pong = static_cast<Pong*>(data); + pong->Update(); +} + +} // namespace AsyncCallbacks + +class UpdateScheduler { + public: + UpdateScheduler(int32_t delay, Pong* pong) + : delay_(delay), pong_(pong) {} + ~UpdateScheduler() { + pp::Core* core = pp::Module::Get()->core(); + core->CallOnMainThread(delay_, pp::CompletionCallback( + AsyncCallbacks::UpdateCallback, pong_)); + } + + private: + int32_t delay_; // milliseconds + Pong* pong_; // weak +}; + +Pong::Pong(PP_Instance instance) + : pp::Instance(instance), + bytes_to_read_(0), + offset_(0), + file_io_(NULL), + file_ref_(NULL), + file_system_(NULL), + view_(NULL), + delta_x_(0), + delta_y_(0), + player_score_(0), + computer_score_(0) { + // Request to receive input events. + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD); +} + +Pong::~Pong() { + delete view_; + file_io_->Close(); + delete file_io_; + delete file_ref_; + delete file_system_; +} + +bool Pong::Init(uint32_t argc, const char* argn[], const char* argv[]) { + view_ = new View(this); + // Read the score from file. + file_system_ = new pp::FileSystem(this, PP_FILESYSTEMTYPE_LOCALPERSISTENT); + file_ref_ = new pp::FileRef(*file_system_, "/pong_score"); + // We kick off a series of callbacks which open a file, query the file for + // it's length in bytes, read the file contents, and then update the score + // display based on the file contents. + int32_t rv = file_system_->Open( + 1024, pp::CompletionCallback(AsyncCallbacks::FileSystemOpenCallback, + this)); + if (rv != PP_OK_COMPLETIONPENDING) { + PostMessage(pp::Var("ERROR: Could not open local persistent file system.")); + return true; + } + UpdateScoreDisplay(); + UpdateScheduler(kUpdateInterval, this); + return true; +} + +void Pong::DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + pp::Size view_size = view_->GetSize(); + const bool view_was_empty = view_size.IsEmpty(); + view_->UpdateView(position, clip, this); + if (view_was_empty) + ResetPositions(); +} + +bool Pong::HandleInputEvent(const pp::InputEvent& event) { + if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP) { + // By notifying the browser mouse clicks are handled, the application window + // is able to get focus and receive key events. + return true; + } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYUP) { + pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); + return view_->KeyUp(key); + } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) { + pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); + return view_->KeyDown(key); + } + return false; +} + + +void Pong::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) + return; + std::string message = var_message.AsString(); + if (message == kResetScoreMethodId) { + ResetScore(); + } +} + +void Pong::Update() { + // Schedule another update + UpdateScheduler(kUpdateInterval, this); + + const uint32_t key_code = view_->last_key_code(); + if (key_code == kSpaceBar) { + ResetPositions(); + return; + } + if (ball_.right() < court_.x()) { + ++computer_score_; + if (computer_score_ > kMaxPointsAllowed) { + ResetScore(); + } else { + WriteScoreToFile(); + UpdateScoreDisplay(); + } + ResetPositions(); + return; + } + if (ball_.x() > court_.right()) { + ++player_score_; + if (player_score_ > kMaxPointsAllowed) { + ResetScore(); + } else { + WriteScoreToFile(); + UpdateScoreDisplay(); + } + ResetPositions(); + return; + } + // Update human controlled paddle + if (key_code == kUpArrow) { + left_paddle_.Offset(0, -kUpdateDistance); + if (left_paddle_.y() - 1 < court_.y()) { + left_paddle_.Offset(0, court_.y() - left_paddle_.y() + 1); + } + } else if (key_code == kDownArrow) { + left_paddle_.Offset(0, kUpdateDistance); + if (left_paddle_.bottom() + 1 > court_.bottom()) { + left_paddle_.Offset(0, court_.bottom() - left_paddle_.bottom() - 1); + } + } + + // Update AI controlled paddle + BallDirection direction = RightPaddleNextMove(); + if (direction == kUpDirection) { + right_paddle_.Offset(0, -kUpdateDistance); + if (right_paddle_.y() < court_.y() + 1) { + right_paddle_.Offset(0, court_.y() - right_paddle_.y() + 1); + } + } else if (direction == kDownDirection) { + right_paddle_.Offset(0, kUpdateDistance); + if (right_paddle_.bottom() > court_.bottom() - 1) { + right_paddle_.Offset(0, court_.bottom() - right_paddle_.bottom() - 1); + } + } + + // Bounce ball off bottom of screen + if (ball_.bottom() >= court_.bottom() - 1) { + ball_.Offset(0, court_.bottom() - ball_.bottom() - 1); + delta_y_ = -delta_y_; + } + // Bounce ball off top of screen + if (ball_.y() <= court_.y() + 1) { + ball_.Offset(0, court_.y() - ball_.y() + 1); + delta_y_ = -delta_y_; + } + // Bounce ball off human controlled paddle + if (left_paddle_.Intersects(ball_)) { + delta_x_ = abs(delta_x_); + if (ball_.CenterPoint().y() < + left_paddle_.y() + left_paddle_.height() / 5) { + delta_y_ -= kUpdateDistance; + if (delta_y_ == 0) + delta_y_ = -kUpdateDistance; + } else if (ball_.CenterPoint().y() > + left_paddle_.bottom() - left_paddle_.height() / 5) { + delta_y_ += kUpdateDistance; + if (delta_y_ == 0) + delta_y_ = kUpdateDistance; + } + } + // Bounce ball off ai controlled paddle + if (right_paddle_.Intersects(ball_)) { + delta_x_ = -abs(delta_x_); + if (ball_.CenterPoint().y() > + right_paddle_.bottom() - right_paddle_.height() / 5) { + delta_y_ += kUpdateDistance; + if (delta_y_ == 0) + delta_y_ = kUpdateDistance; + } else if (ball_.CenterPoint().y() < + right_paddle_.y() + right_paddle_.height() / 5) { + delta_y_ -= kUpdateDistance; + if (delta_y_ == 0) + delta_y_ -= kUpdateDistance; + } + } + + // Move ball + ball_.Offset(delta_x_, delta_y_); + + view_->set_ball_rect(ball_); + view_->set_left_paddle_rect(left_paddle_); + view_->set_right_paddle_rect(right_paddle_); + view_->Draw(); +} + +void Pong::UpdateScoreFromBuffer() { + size_t pos = bytes_buffer_.find_first_of(':'); + player_score_ = ::atoi(bytes_buffer_.substr(0, pos).c_str()); + computer_score_ = ::atoi(bytes_buffer_.substr(pos + 1).c_str()); + UpdateScoreDisplay(); +} + +void Pong::UpdateScoreFromFile() { + file_io_ = new pp::FileIO(this); + file_io_->Open(*file_ref_, + PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE | + PP_FILEOPENFLAG_CREATE, + pp::CompletionCallback(AsyncCallbacks::FileOpenCallback, + this)); +} + +void Pong::WriteScoreToFile() { + if (file_io_ == NULL) + return; + // Write the score in <player score>:<computer score> format. + size_t score_string_length = 1 + (player_score_ ? log(player_score_) : 1) + 1 + + (computer_score_ ? log(computer_score_) : 1) + 1; + bytes_buffer_.resize(score_string_length); + snprintf(&bytes_buffer_[0], bytes_buffer_.length(), "%i:%i", player_score_, + computer_score_); + offset_ = 0; // overwrite score in file. + file_io_->Write(offset_, + bytes_buffer_.c_str(), + bytes_buffer_.length(), + pp::CompletionCallback(AsyncCallbacks::WriteCallback, this)); +} + +void Pong::ResetPositions() { + pp::Size court_size = view_->GetSize(); + pp::Rect court_rect(court_size); + court_.SetRect(court_rect); + + pp::Rect rect; + rect.set_width(20); + rect.set_height(20); + rect.set_x((court_rect.x() + court_rect.width()) / 2 - rect.width() / 2); + rect.set_y(court_rect.y() + court_rect.height() - rect.height()); + ball_.SetRect(rect); + + const float paddle_width = 10; + const float paddle_height = 99; + const float paddle_pos_y = + (court_rect.y() + court_rect.height()) / 2 - rect.height() / 2; + rect.set_width(paddle_width); + rect.set_height(paddle_height); + rect.set_x(court_rect.x() + court_rect.width() / 5 + 1); + rect.set_y(paddle_pos_y); + left_paddle_.SetRect(rect); + + rect.set_width(paddle_width); + rect.set_height(paddle_height); + rect.set_x(court_rect.x() + 4 * court_rect.width() / 5 - rect.width() - 1); + rect.set_y(paddle_pos_y); + right_paddle_.SetRect(rect); + + delta_x_ = delta_y_ = kUpdateDistance; +} + +void Pong::ResetScore() { + player_score_ = 0; + computer_score_ = 0; + UpdateScoreDisplay(); +} + +Pong::BallDirection Pong::RightPaddleNextMove() const { + static int32_t last_ball_y = 0; + int32_t ball_y = ball_.CenterPoint().y(); + BallDirection ball_direction = + ball_y < last_ball_y ? kUpDirection : kDownDirection; + last_ball_y = ball_y; + + if (ball_y < right_paddle_.y()) + return kUpDirection; + if (ball_y > right_paddle_.bottom()) + return kDownDirection; + return ball_direction; +} + +void Pong::UpdateScoreDisplay() { + if (file_io_ == NULL) + return; // Since we cant't save the score to file, do nothing and return. + size_t message_length = 18 + (player_score_ ? log(player_score_) : 1) + 1 + + (computer_score_ ? log(computer_score_) : 1) + 1; + std::string score_message(message_length, '\0'); + snprintf(&score_message[0], score_message.length(), + "You: %i Computer: %i", player_score_, computer_score_); + PostMessage(pp::Var(score_message)); +} + +} // namespace pong diff --git a/native_client_sdk/src/examples/pong/pong.h b/native_client_sdk/src/examples/pong/pong.h new file mode 100644 index 0000000..8f38e3e --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_PONG_PONG_H_ +#define EXAMPLES_PONG_PONG_H_ + +#include <string> + +#include "ppapi/c/pp_file_info.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" + +namespace pp { +class FileIO; +class FileRef; +class FileSystem; +class Rect; +} // namespace pp + +namespace pong { + +class View; + +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// nacl="pong.nmf" +class Pong : public pp::Instance { + public: + explicit Pong(PP_Instance instance); + virtual ~Pong(); + + // Open the file (if available) that stores the game scores. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Update the graphics context to the new size, and regenerate |pixel_buffer_| + // to fit the new size as well. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + virtual bool HandleInputEvent(const pp::InputEvent& event); + + // Called by the browser to handle the postMessage() call in Javascript. + // The message in this case is expected to contain the string 'update', or + // 'resetScore' in order to invoke either the Update or ResetScore function + // respectively. + virtual void HandleMessage(const pp::Var& var_message); + + void Update(); + void UpdateScoreFromBuffer(); + void UpdateScoreFromFile(); + void WriteScoreToFile(); + + PP_FileInfo file_info_; + int32_t bytes_to_read_; + int64_t offset_; + pp::FileIO* file_io_; + pp::FileRef* file_ref_; + pp::FileSystem* file_system_; + std::string bytes_buffer_; + + private: + Pong(const Pong&); // Disallow copy + + enum BallDirection { + kUpDirection = 0, + kDownDirection + }; + void ResetPositions(); + void ResetScore(); + BallDirection RightPaddleNextMove() const; + void UpdateScoreDisplay(); + + View* view_; + pp::Rect left_paddle_; + pp::Rect right_paddle_; + pp::Rect ball_; + pp::Rect court_; + int32_t delta_x_; + int32_t delta_y_; + int player_score_; + int computer_score_; +}; + +} // namespace pong + +#endif // EXAMPLES_PONG_PONG_H_ diff --git a/native_client_sdk/src/examples/pong/pong.html b/native_client_sdk/src/examples/pong/pong.html new file mode 100644 index 0000000..59a5915 --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong.html @@ -0,0 +1,74 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Pong</title> + <script type="text/javascript"> + var pong = null; + var paintInterval = null; + + // Handle a message coming from the NaCl module. The message payload is + // assumed to contain the current game score. Update the score text + // display with this value. + // Note that errors are also sent to this handler. A message starting + // with 'ERROR' is considered an error, all other strings are assumed + // to be scores. + function handleMessage(message_event) { + if (message_event.data.indexOf('ERROR') == 0) { + document.getElementById('error_log').innerHTML = message_event.data; + } else { + document.getElementById('score').innerHTML = message_event.data; + } + } + + function pageDidUnload() { + clearInterval(paintInterval); + } + function resetScore() { + pong = document.getElementById('pong'); + pong.postMessage("resetScore"); + } + </script> + </head> + <body id="bodyId" onunload="pageDidUnload()"> + <h1>Pong</h1> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the + runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl + module. To load the debug versions of your .nexes, set the 'src' + attribute to the _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + window.webkitStorageInfo.requestQuota(PERSISTENT, 1024); + var listener = document.getElementById('listener') + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="pong" + width=800 height=600 + src="pong.nmf" + type="application/x-nacl" /> + </div> + <br /> + <p id="score"> + </p> + <button onclick="resetScore()">Reset score</button> + <p id="error_log"></p> + </body> +</html> diff --git a/native_client_sdk/src/examples/pong/pong_module.cc b/native_client_sdk/src/examples/pong/pong_module.cc new file mode 100644 index 0000000..c74ea28 --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong_module.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Native Client 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 <ppapi/cpp/module.h> + +#include "examples/pong/pong.h" + +namespace pong { +// The Module class. The browser calls the CreateInstance() method to create +// an instance of your NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class PongModule : public pp::Module { + public: + PongModule() : pp::Module() {} + virtual ~PongModule() {} + + // Create and return a PiGeneratorInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new Pong(instance); + } +}; +} // namespace pong + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new pong::PongModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/pong/view.cc b/native_client_sdk/src/examples/pong/view.cc new file mode 100644 index 0000000..4bb11b9 --- /dev/null +++ b/native_client_sdk/src/examples/pong/view.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2011 The Native Client 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 "examples/pong/view.h" + +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/point.h" +#include "ppapi/cpp/var.h" + +// Input event key codes. PPAPI uses Windows Virtual key codes. +const uint32_t kSpaceBar = 0x20; +const uint32_t kUpArrow = 0x26; +const uint32_t kDownArrow = 0x28; + +namespace { + +const uint32_t kOpaqueColorMask = 0xff000000; // Opaque pixels. +const uint32_t kWhiteMask = 0xffffff; + +// This is called by the browser when the 2D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<pong::View*>(data)->set_flush_pending(false); +} + +} // namespace + +namespace pong { +View::View(pp::Instance* instance) + : instance_(instance), last_key_code_(0x0), flush_pending_(false), + graphics_2d_context_(NULL), pixel_buffer_(NULL) {} + +View::~View() { + DestroyContext(); + delete pixel_buffer_; +} + +pp::Size View::GetSize() const { + pp::Size size; + if (graphics_2d_context_) { + size.SetSize(graphics_2d_context_->size().width(), + graphics_2d_context_->size().height()); + } + return size; +} + +bool View::KeyDown(const pp::KeyboardInputEvent& key) { + last_key_code_ = key.GetKeyCode(); + if (last_key_code_ == kSpaceBar || last_key_code_ == kUpArrow || + last_key_code_ == kDownArrow) + return true; + return false; +} + +bool View::KeyUp(const pp::KeyboardInputEvent& key) { + if (last_key_code_ == key.GetKeyCode()) { + last_key_code_ = 0x0; // Indicates key code is not set. + } + return false; +} + +void View::Draw() { + uint32_t* pixels = static_cast<uint32_t*>(pixel_buffer_->data()); + if (NULL == pixels) + return; + // Clear the buffer + const int32_t height = pixel_buffer_->size().height(); + const int32_t width = pixel_buffer_->size().width(); + for (int32_t py = 0; py < height; ++py) { + for (int32_t px = 0; px < width; ++px) { + const int32_t pos = px + py * width; + uint32_t color = kOpaqueColorMask; + // Draw the paddles + if (left_paddle_rect_.Contains(px, py) || + right_paddle_rect_.Contains(px, py)) { + color |= kWhiteMask; + } else { + pp::Point center_point = ball_rect_.CenterPoint(); + float radius = ball_rect_.width() / 2; + float distance_x = px - center_point.x(); + float distance_y = py - center_point.y(); + float distance = + sqrt(distance_x * distance_x + distance_y * distance_y); + // Draw the ball + if (distance <= radius) + color |= kWhiteMask; + } + pixels[pos] = color; + } + } + + FlushPixelBuffer(); +} + +void View::UpdateView(const pp::Rect& position, + const pp::Rect& clip, + pp::Instance* instance) { + const int32_t width = + pixel_buffer_ ? pixel_buffer_->size().width() : 0; + const int32_t height = + pixel_buffer_ ? pixel_buffer_->size().height() : 0; + + if (position.size().width() == width && + position.size().height() == height) + return; // Size didn't change, no need to update anything. + + // Create a new device context with the new size. + DestroyContext(); + CreateContext(position.size(), instance); + // Delete the old pixel buffer and create a new one. + delete pixel_buffer_; + pixel_buffer_ = NULL; + if (graphics_2d_context_ != NULL) { + pixel_buffer_ = new pp::ImageData(instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, + graphics_2d_context_->size(), + false); + } +} + +void View::CreateContext(const pp::Size& size, pp::Instance* instance) { + if (IsContextValid()) + return; + graphics_2d_context_ = new pp::Graphics2D(instance, size, + false); + if (!instance->BindGraphics(*graphics_2d_context_)) { + instance_->PostMessage(pp::Var("ERROR: Couldn't bind the device context")); + } +} + +void View::DestroyContext() { + if (!IsContextValid()) + return; + delete graphics_2d_context_; + graphics_2d_context_ = NULL; +} + +void View::FlushPixelBuffer() { + if (!IsContextValid()) + return; + // Note that the pixel lock is held while the buffer is copied into the + // device context and then flushed. + graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point()); + if (flush_pending_) + return; + flush_pending_ = true; + graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this)); +} + +bool View::IsContextValid() const { + return graphics_2d_context_ != NULL; +} + +} // namespace pong diff --git a/native_client_sdk/src/examples/pong/view.h b/native_client_sdk/src/examples/pong/view.h new file mode 100644 index 0000000..269ff99 --- /dev/null +++ b/native_client_sdk/src/examples/pong/view.h @@ -0,0 +1,75 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_PONG_VIEW_H_ +#define EXAMPLES_PONG_VIEW_H_ + +#include "ppapi/cpp/rect.h" + +namespace pp { +class Graphics2D; +class ImageData; +class Instance; +class KeyboardInputEvent; +class Rect; +class Size; +} // namespace pp + +namespace pong { + +class View { + public: + explicit View(pp::Instance* instance); + ~View(); + + const uint32_t& last_key_code() const { + return last_key_code_; + } + void set_left_paddle_rect(const pp::Rect& left_paddle_rect) { + left_paddle_rect_ = left_paddle_rect; + } + void set_right_paddle_rect(const pp::Rect& right_paddle_rect) { + right_paddle_rect_ = right_paddle_rect; + } + void set_ball_rect(const pp::Rect& ball_rect) { + ball_rect_ = ball_rect; + } + void set_flush_pending(bool flush_pending) { + flush_pending_ = flush_pending; + } + pp::Size GetSize() const; + bool KeyDown(const pp::KeyboardInputEvent& key); + bool KeyUp(const pp::KeyboardInputEvent& key); + void Draw(); + void UpdateView(const pp::Rect& position, + const pp::Rect& clip, + pp::Instance* instance); + + private: + pp::Instance* const instance_; // weak + // Create and initialize the 2D context used for drawing. + void CreateContext(const pp::Size& size, pp::Instance* instance); + // Destroy the 2D drawing context. + void DestroyContext(); + // Push the pixels to the browser, then attempt to flush the 2D context. If + // there is a pending flush on the 2D context, then update the pixels only + // and do not flush. + void FlushPixelBuffer(); + bool IsContextValid() const; + + uint32_t last_key_code_; + // Geometry for drawing + pp::Rect left_paddle_rect_; + pp::Rect right_paddle_rect_; + pp::Rect ball_rect_; + // Drawing stuff + bool flush_pending_; + pp::Graphics2D* graphics_2d_context_; + pp::ImageData* pixel_buffer_; +}; + + +} // namespace pong + +#endif // EXAMPLES_PONG_VIEW_H_ diff --git a/native_client_sdk/src/examples/scons b/native_client_sdk/src/examples/scons new file mode 100755 index 0000000..7742fd7 --- /dev/null +++ b/native_client_sdk/src/examples/scons @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +readonly SCRIPT_DIR="$(dirname "$0")" +readonly SCRIPT_DIR_ABS="$(cd "${SCRIPT_DIR}" ; pwd -P)" + +# NACL_SDK_ROOT must be set. +if [ x"${NACL_SDK_ROOT}"x == "xx" ] ; then + echo "Error: NACL_SDK_ROOT is not set." + exit 1; +fi + +# NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +# usually NACL_SDK_ROOT - within which the toolchain for the target platform +# are found. +# Replace the platform with the name of your target platform. For example, to +# build applications that target the pepper_17 API, set +# NACL_TARGET_PLATFORM="pepper_17" +if [ x"${NACL_TARGET_PLATFORM}"x == "xx" ] ; then + export NACL_TARGET_PLATFORM="pepper_17" +fi + +readonly NACL_PLATFORM_DIR="${NACL_SDK_ROOT}/${NACL_TARGET_PLATFORM}" +readonly BASE_SCRIPT="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/script/scons" + +export SCONS_LIB_DIR="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine" +export PYTHONPATH="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine" +# We have to do this because scons overrides PYTHONPATH and does not preserve +# what is provided by the OS. The custom variable name won't be overwritten. +export PYMOX="${NACL_PLATFORM_DIR}/third_party/pymox" + +"${BASE_SCRIPT}" --file=build.scons \ + --site-dir="${NACL_PLATFORM_DIR}/build_tools/nacl_sdk_scons" \ + $* + diff --git a/native_client_sdk/src/examples/scons.bat b/native_client_sdk/src/examples/scons.bat new file mode 100755 index 0000000..2a67758 --- /dev/null +++ b/native_client_sdk/src/examples/scons.bat @@ -0,0 +1,43 @@ +@echo off + +:: Copyright (c) 2011 The Native Client Authors. All rights reserved. +:: Use of this source code is governed by a BSD-style license that can be +:: found in the LICENSE file. + +setlocal + +:: NACL_SDK_ROOT must be set. +if not defined NACL_SDK_ROOT ( + echo Error: NACL_SDK_ROOT is not set. + echo Please set NACL_SDK_ROOT to the full path of the Native Client SDK. + echo For example: + echo set NACL_SDK_ROOT=D:\nacl_sdk + goto end +) + +:: NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +:: usually NACL_SDK_ROOT - within which the toolchain for the target platform +:: are found. +:: Replace the platform with the name of your target platform. For example, to +:: build applications that target the pepper_17 API, set +:: NACL_TARGET_PLATFORM=pepper_17 +if not defined NACL_TARGET_PLATFORM ( + set NACL_TARGET_PLATFORM=pepper_17 +) + +set NACL_PLATFORM_DIR=%NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM% + +set SCONS_LIB_DIR=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine +set PYTHONPATH=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine + +:: We have to do this because scons overrides PYTHONPATH and does not preserve +:: what is provided by the OS. The custom variable name won't be overwritten. +set PYMOX=%NACL_PLATFORM_DIR%\third_party\pymox + +:: Run the included copy of scons. +python -O -OO "%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\script\scons" ^ +--warn no-visual-c-missing ^ +--file=build.scons ^ +--site-dir="%NACL_PLATFORM_DIR%\build_tools\nacl_sdk_scons" %* + +:end diff --git a/native_client_sdk/src/examples/sine_synth/build.scons b/native_client_sdk/src/examples/sine_synth/build.scons new file mode 100644 index 0000000..5b10dc8 --- /dev/null +++ b/native_client_sdk/src/examples/sine_synth/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='sine_synth', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['sine_synth.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'sine_synth') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('sine_synth') + +app_files = [ + 'sine_synth.html', + 'sine_synth.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/sine_synth/sine_synth.cc b/native_client_sdk/src/examples/sine_synth/sine_synth.cc new file mode 100644 index 0000000..2305020 --- /dev/null +++ b/native_client_sdk/src/examples/sine_synth/sine_synth.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2011 The Native Client 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 <cassert> +#include <cmath> +#include <limits> +#include <sstream> +#include "ppapi/cpp/audio.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace { +const char* const kPlaySoundId = "playSound"; +const char* const kStopSoundId = "stopSound"; +const char* const kSetFrequencyId = "setFrequency"; +static const char kMessageArgumentSeparator = ':'; + +const double kDefaultFrequency = 440.0; +const double kPi = 3.141592653589; +const double kTwoPi = 2.0 * kPi; +// The sample count we will request. +const uint32_t kSampleFrameCount = 4096u; +// Only supporting stereo audio for now. +const uint32_t kChannels = 2u; +} // namespace + +namespace sine_synth { +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// src="sine_synth.nmf" +class SineSynthInstance : public pp::Instance { + public: + explicit SineSynthInstance(PP_Instance instance) + : pp::Instance(instance), + frequency_(kDefaultFrequency), + theta_(0), + sample_frame_count_(kSampleFrameCount) {} + virtual ~SineSynthInstance() {} + + // Called by the browser once the NaCl module is loaded and ready to + // initialize. Creates a Pepper audio context and initializes it. Returns + // true on success. Returning false causes the NaCl module to be deleted and + // no other functions to be called. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called by the browser to handle the postMessage() call in Javascript. + // |var_message| is expected to be a string that contains the name of the + // method to call. Note that the setFrequency method takes a single + // parameter, the frequency. The frequency parameter is encoded as a string + // and appended to the 'setFrequency' method name after a ':'. Examples + // of possible message strings are: + // playSound + // stopSound + // setFrequency:880 + // If |var_message| is not a recognized method name, this method does nothing. + virtual void HandleMessage(const pp::Var& var_message); + + // Set the frequency of the sine wave to |frequency|. Posts a message back + // to the browser with the new frequency value. + void SetFrequency(double frequency); + + // The frequency property accessor. + double frequency() const { return frequency_; } + + private: + static void SineWaveCallback(void* samples, + uint32_t buffer_size, + void* data) { + SineSynthInstance* sine_synth_instance = + reinterpret_cast<SineSynthInstance*>(data); + const double frequency = sine_synth_instance->frequency(); + const double delta = kTwoPi * frequency / PP_AUDIOSAMPLERATE_44100; + const int16_t max_int16 = std::numeric_limits<int16_t>::max(); + + int16_t* buff = reinterpret_cast<int16_t*>(samples); + + // Make sure we can't write outside the buffer. + assert(buffer_size >= (sizeof(*buff) * kChannels * + sine_synth_instance->sample_frame_count_)); + + for (size_t sample_i = 0; + sample_i < sine_synth_instance->sample_frame_count_; + ++sample_i, sine_synth_instance->theta_ += delta) { + // Keep theta_ from going beyond 2*Pi. + if (sine_synth_instance->theta_ > kTwoPi) { + sine_synth_instance->theta_ -= kTwoPi; + } + double sin_value(std::sin(sine_synth_instance->theta_)); + int16_t scaled_value = static_cast<int16_t>(sin_value * max_int16); + for (size_t channel = 0; channel < kChannels; ++channel) { + *buff++ = scaled_value; + } + } + } + + pp::Audio audio_; + double frequency_; + + // The last parameter sent to the sin function. Used to prevent sine wave + // skips on buffer boundaries. + double theta_; + + // The count of sample frames per channel in an audio buffer. + uint32_t sample_frame_count_; +}; + +bool SineSynthInstance::Init(uint32_t argc, + const char* argn[], + const char* argv[]) { + // Ask the device for an appropriate sample count size. + sample_frame_count_ = + pp::AudioConfig::RecommendSampleFrameCount(PP_AUDIOSAMPLERATE_44100, + kSampleFrameCount); + audio_ = pp::Audio(this, + pp::AudioConfig(this, + PP_AUDIOSAMPLERATE_44100, + sample_frame_count_), + SineWaveCallback, + this); + return true; +} + +void SineSynthInstance::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + return; + } + std::string message = var_message.AsString(); + if (message == kPlaySoundId) { + audio_.StartPlayback(); + } else if (message == kStopSoundId) { + audio_.StopPlayback(); + } else if (message.find(kSetFrequencyId) == 0) { + // The argument to setFrequency is everything after the first ':'. + size_t sep_pos = message.find_first_of(kMessageArgumentSeparator); + if (sep_pos != std::string::npos) { + std::string string_arg = message.substr(sep_pos + 1); + // Got the argument value as a string: try to convert it to a number. + std::istringstream stream(string_arg); + double double_value; + if (stream >> double_value) { + SetFrequency(double_value); + return; + } + } + } +} + +void SineSynthInstance::SetFrequency(double frequency) { + frequency_ = frequency; + PostMessage(pp::Var(frequency_)); +} + +// The Module class. The browser calls the CreateInstance() method to create +// an instance of your NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class SineSynthModule : public pp::Module { + public: + SineSynthModule() : pp::Module() {} + ~SineSynthModule() {} + + // Create and return a HelloWorldInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new SineSynthInstance(instance); + } +}; + +} // namespace sine_synth + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new sine_synth::SineSynthModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/sine_synth/sine_synth.html b/native_client_sdk/src/examples/sine_synth/sine_synth.html new file mode 100644 index 0000000..3d1609b --- /dev/null +++ b/native_client_sdk/src/examples/sine_synth/sine_synth.html @@ -0,0 +1,80 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> +<!-- +Copyright (c) 2011 The Native Client Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<head> + <title>Sine Wave Synthesizer</title> + <script type="text/javascript"> + sineSynth = null; // Global application object. + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + sineSynth = document.getElementById('sineSynth'); + document.getElementById('frequency_field').value = 440; + } + + // Handle a message coming from the NaCl module. The message payload + // contains the frequency value. Update the frequency field with this + // value. + function handleMessage(message_event) { + document.getElementById('frequency_field').value = message_event.data; + } + + function toggleSound(flag) { + sineSynth.postMessage('setFrequency:' + + document.getElementById('frequency_field').value); + if (flag) { + sineSynth.postMessage('playSound'); + } else { + sineSynth.postMessage('stopSound'); + } + } + + function changeFrequency(freq) { + sineSynth.postMessage('setFrequency:' + freq); + } + </script> +</head> + +<body id="bodyId"> + <h1>Sine Wave Synthesizer</h1> + <p>Click the button to start and stop the sine wave playing.</p> + <button onclick="toggleSound(true)">Play</button> + <button onclick="toggleSound(false)">Stop</button> + <p>Enter the frequency of the sine wave:</p> + <input type="text" size="15" id="frequency_field" + value="#undef" onchange="changeFrequency(this.value)" /> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="sineSynth" + width=0 height=0 + src="sine_synth.nmf" + type="application/x-nacl" /> + </div> +</body> +</html> diff --git a/native_client_sdk/src/examples/tumbler/bind.js b/native_client_sdk/src/examples/tumbler/bind.js new file mode 100644 index 0000000..92fbbd2 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/bind.js @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements an extension to Function object that + * lets you bind a scope for |this| to a function. + */ + +/** + * Bind a scope to a function. Used to bind an object to |this| for event + * handlers. + * @param {!Object} scope The scope in which the function executes. |scope| + * becomes |this| during function execution. + * @return {function} the bound version of the original function. + */ +Function.prototype.bind = function(scope) { + var boundContext = this; + return function() { + return boundContext.apply(scope, arguments); + } +} diff --git a/native_client_sdk/src/examples/tumbler/build.scons b/native_client_sdk/src/examples/tumbler/build.scons new file mode 100644 index 0000000..4c42b7c --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/build.scons @@ -0,0 +1,55 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='tumbler', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + LIBS=['ppapi_gles2'], + ) + +sources = [ + 'cube.cc', + 'opengl_context.cc', + 'scripting_bridge.cc', + 'shader_util.cc', + 'transforms.cc', + 'tumbler.cc', + 'tumbler_module.cc', + ] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'tumbler') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('tumbler') + +app_files = [ + 'tumbler.html', + 'tumbler.nmf', + 'bind.js', + 'dragger.js', + 'trackball.js', + 'tumbler.js', + 'vector3.js', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/tumbler/callback.h b/native_client_sdk/src/examples/tumbler/callback.h new file mode 100644 index 0000000..4d67262 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/callback.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CALLBACK_H_ +#define EXAMPLES_TUMBLER_CALLBACK_H_ + +#include <map> +#include <string> +#include <vector> + +namespace tumbler { + +class ScriptingBridge; + +// Templates used to support method call-backs when a method or property is +// accessed from the browser code. + +// Class suite used to publish a method name to Javascript. Typical use is +// like this: +// photo::MethodCallback<Calculator>* calculate_callback_; +// calculate_callback_ = +// new scripting::MethodCallback<Calculator>(this, +// &Calculator::Calculate); +// bridge->AddMethodNamed("calculate", calculate_callback_); +// ... +// delete calculate_callback_; +// +// The caller must delete the callback. + +// Methods get parameters as a dictionary that maps parameter names to values. +typedef std::map<std::string, std::string> MethodParameter; + +// Pure virtual class used in STL containers. +class MethodCallbackExecutor { + public: + virtual ~MethodCallbackExecutor() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) = 0; +}; + +template <class T> +class MethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*Method)( + const ScriptingBridge& bridge, + const MethodParameter& parameters); + + MethodCallback(T* instance, Method method) + : instance_(instance), method_(method) {} + virtual ~MethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->method_))(bridge, parameters); + } + + private: + T* instance_; + Method method_; +}; + +template <class T> +class ConstMethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*ConstMethod)( + const ScriptingBridge& bridge, + const MethodParameter& parameters) const; + + ConstMethodCallback(const T* instance, ConstMethod method) + : instance_(instance), const_method_(method) {} + virtual ~ConstMethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->const_method_))(bridge, parameters); + } + + private: + const T* instance_; + ConstMethod const_method_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CALLBACK_H_ + diff --git a/native_client_sdk/src/examples/tumbler/cube.cc b/native_client_sdk/src/examples/tumbler/cube.cc new file mode 100644 index 0000000..c062c81 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/cube.cc @@ -0,0 +1,267 @@ +// Copyright (c) 2011 The Native Client 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 "examples/tumbler/cube.h" + +#include <algorithm> + +#include "examples/tumbler/shader_util.h" +#include "examples/tumbler/transforms.h" + +namespace tumbler { + +static const size_t kVertexCount = 24; +static const int kIndexCount = 36; + +Cube::Cube(SharedOpenGLContext opengl_context) + : opengl_context_(opengl_context), + width_(1), + height_(1) { + eye_[0] = eye_[1] = 0.0f; + eye_[2] = 2.0f; + orientation_[0] = 0.0f; + orientation_[1] = 0.0f; + orientation_[2] = 0.0f; + orientation_[3] = 1.0f; +} + +Cube::~Cube() { + glDeleteBuffers(3, cube_vbos_); + glDeleteProgram(shader_program_object_); +} + +void Cube::PrepareOpenGL() { + CreateShaders(); + CreateCube(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glEnable(GL_DEPTH_TEST); +} + +void Cube::Resize(int width, int height) { + width_ = std::max(width, 1); + height_ = std::max(height, 1); + // Set the viewport + glViewport(0, 0, width_, height_); + // Compute the perspective projection matrix with a 60 degree FOV. + GLfloat aspect = static_cast<GLfloat>(width_) / static_cast<GLfloat>(height_); + transform_4x4::LoadIdentity(perspective_proj_); + transform_4x4::Perspective(perspective_proj_, 60.0f, aspect, 1.0f, 20.0f); +} + +void Cube::Draw() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Compute a new model-view matrix, then use that to make the composite + // model-view-projection matrix: MVP = MV . P. + GLfloat model_view[16]; + ComputeModelViewTransform(model_view); + transform_4x4::Multiply(mvp_matrix_, model_view, perspective_proj_); + + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glUseProgram(shader_program_object_); + glEnableVertexAttribArray(position_location_); + glVertexAttribPointer(position_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glEnableVertexAttribArray(color_location_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glVertexAttribPointer(color_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glUniformMatrix4fv(mvp_location_, 1, GL_FALSE, mvp_matrix_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glDrawElements(GL_TRIANGLES, kIndexCount, GL_UNSIGNED_SHORT, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +bool Cube::CreateShaders() { + const char vertex_shader_src[] = + "uniform mat4 u_mvpMatrix; \n" + "attribute vec4 a_position; \n" + "attribute vec3 a_color; \n" + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " v_color.xyz = a_color; \n" + " v_color.w = 1.0; \n" + " gl_Position = u_mvpMatrix * a_position; \n" + "} \n"; + + const char fragment_shader_src[] = + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " gl_FragColor = v_color; \n" + "} \n"; + + // Load the shaders and get a linked program object + shader_program_object_ = + shader_util::CreateProgramFromVertexAndFragmentShaders( + vertex_shader_src, fragment_shader_src); + if (shader_program_object_ == 0) + return false; + position_location_ = glGetAttribLocation(shader_program_object_, + "a_position"); + color_location_ = glGetAttribLocation(shader_program_object_, "a_color"); + mvp_location_ = glGetUniformLocation(shader_program_object_, "u_mvpMatrix"); + return true; +} + +void Cube::CreateCube() { + static const GLfloat cube_vertices[] = { + // Vertex coordinates interleaved with color values + // Bottom + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, -0.5f, + // Top + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + // Back + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + // Front + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + // Left + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + // Right + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f + }; + + static const GLfloat cube_colors[] = { + // Vertex coordinates interleaved with color values + // Bottom + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + // Back + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + // Front + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + // Left + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + // Right + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0 + }; + + static const GLushort cube_indices[] = { + // Bottom + 0, 2, 1, + 0, 3, 2, + // Top + 4, 5, 6, + 4, 6, 7, + // Back + 8, 9, 10, + 8, 10, 11, + // Front + 12, 15, 14, + 12, 14, 13, + // Left + 16, 17, 18, + 16, 18, 19, + // Right + 20, 23, 22, + 20, 22, 21 + }; + + // Generate the VBOs and upload them to the graphics context. + glGenBuffers(3, cube_vbos_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_vertices, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_colors, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + kIndexCount * sizeof(GL_UNSIGNED_SHORT), + cube_indices, + GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +void Cube::ComputeModelViewTransform(GLfloat* model_view) { + // This method takes into account the possiblity that |orientation_| + // might not be normalized. + double sqrx = orientation_[0] * orientation_[0]; + double sqry = orientation_[1] * orientation_[1]; + double sqrz = orientation_[2] * orientation_[2]; + double sqrw = orientation_[3] * orientation_[3]; + double sqrLength = 1.0 / (sqrx + sqry + sqrz + sqrw); + + transform_4x4::LoadIdentity(model_view); + model_view[0] = (sqrx - sqry - sqrz + sqrw) * sqrLength; + model_view[5] = (-sqrx + sqry - sqrz + sqrw) * sqrLength; + model_view[10] = (-sqrx - sqry + sqrz + sqrw) * sqrLength; + + double temp1 = orientation_[0] * orientation_[1]; + double temp2 = orientation_[2] * orientation_[3]; + model_view[1] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[4] = 2.0 * (temp1 - temp2) * sqrLength; + + temp1 = orientation_[0] * orientation_[2]; + temp2 = orientation_[1] * orientation_[3]; + model_view[2] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[8] = 2.0 * (temp1 + temp2) * sqrLength; + temp1 = orientation_[1] * orientation_[2]; + temp2 = orientation_[0] * orientation_[3]; + model_view[6] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[9] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[3] = 0.0; + model_view[7] = 0.0; + model_view[11] = 0.0; + + // Concatenate the translation to the eye point. + model_view[12] = -eye_[0]; + model_view[13] = -eye_[1]; + model_view[14] = -eye_[2]; + model_view[15] = 1.0; +} + +} // namespace tumbler diff --git a/native_client_sdk/src/examples/tumbler/cube.h b/native_client_sdk/src/examples/tumbler/cube.h new file mode 100644 index 0000000..6a992c7 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/cube.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CUBE_H_ +#define EXAMPLES_TUMBLER_CUBE_H_ + +#include <GLES2/gl2.h> +#include <vector> +#include "examples/tumbler/opengl_context.h" +#include "examples/tumbler/opengl_context_ptrs.h" + +namespace tumbler { + +// The Cube class provides a place to implement 3D rendering. It has a +// frame that it occupies in a browser window. +class Cube { + public: + explicit Cube(SharedOpenGLContext opengl_context); + ~Cube(); + + // Called once when a new RenderContext is first bound to the view. The + // bound context is guaranteed to be current and valid before calling this + // method. + void PrepareOpenGL(); + + // Called whenever the size of the browser view changes. This method is + // called at least once when the view is first made visible. Clamps the + // sizes to 1. + void Resize(int width, int height); + + // Called every time the view need to be drawn. The bound context is + // guaranteed to be current and valid before this method is called. The + // visible portion of the context is flushed to the browser after this + // method returns. + void Draw(); + + // Accessor for width and height. To change these, call Resize. + const int width() const { + return width_; + } + + const int height() const { + return height_; + } + + // Accessor/mutator for the camera orientation. + void GetOrientation(std::vector<float>* orientation) const { + if (!orientation) + return; + (*orientation)[0] = static_cast<float>(orientation_[0]); + (*orientation)[1] = static_cast<float>(orientation_[1]); + (*orientation)[2] = static_cast<float>(orientation_[2]); + (*orientation)[3] = static_cast<float>(orientation_[3]); + } + void SetOrientation(const std::vector<float>& orientation) { + orientation_[0] = static_cast<GLfloat>(orientation[0]); + orientation_[1] = static_cast<GLfloat>(orientation[1]); + orientation_[2] = static_cast<GLfloat>(orientation[2]); + orientation_[3] = static_cast<GLfloat>(orientation[3]); + } + + private: + // Create the shaders used to draw the cube, and link them into a program. + // Initializes |shader_progam_object_|, |position_loction_| and + // |mvp_location_|. + bool CreateShaders(); + + // Generates a cube as a series of GL_TRIANGLE_STRIPs, and initializes + // |index_count_| to the number of indices in the index list used as a VBO. + // Creates the |vbo_ids_| required for the vertex and index data and uploads + // the the VBO data. + void CreateCube(); + + // Build up the model-view transform from the eye and orienation properties. + // Assumes that |model_view| is a 4x4 matrix. + void ComputeModelViewTransform(GLfloat* model_view); + + SharedOpenGLContext opengl_context_; + int width_; + int height_; + GLuint shader_program_object_; // The compiled shaders. + GLint position_location_; // The position attribute location. + GLint color_location_; // The color attribute location. + GLint mvp_location_; // The Model-View-Projection composite matrix. + GLuint cube_vbos_[3]; + GLfloat eye_[3]; // The eye point of the virtual camera. + // The orientation of the virtual camera stored as a quaternion. The + // quaternion is laid out as {{x, y, z}, w}. + GLfloat orientation_[4]; + GLfloat perspective_proj_[16]; + GLfloat mvp_matrix_[16]; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CUBE_H_ diff --git a/native_client_sdk/src/examples/tumbler/dragger.js b/native_client_sdk/src/examples/tumbler/dragger.js new file mode 100644 index 0000000..232d8b5 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/dragger.js @@ -0,0 +1,134 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements a mouse-drag event. It registers for + * mousedown events, and when it sees one, starts capturing mousemove events + * until it gets a mousup event. It manufactures three drag events: the + * DRAG_START, DRAG and DRAG_END. + */ + +// Requires bind + +/** + * Constructor for the Dragger. Register for mousedown events that happen on + * |opt_target|. If |opt_target| is null or undefined, then this object + * observes mousedown on the whole document. + * @param {?Element} opt_target The event target. Defaults to the whole + * document. + * @constructor + */ +tumbler.Dragger = function(opt_target) { + /** + * The event target. + * @type {Element} + * @private + */ + this.target_ = opt_target || document; + + /** + * The array of objects that get notified of drag events. Each object in + * this array get sent a handleStartDrag(), handleDrag() and handleEndDrag() + * message. + * @type {Array.<Object>} + * @private + */ + this.listeners_ = []; + + /** + * Flag to indicate whether the object is in a drag sequence or not. + * @type {boolean} + * @private + */ + this.isDragging_ = false; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed on mouse up. + * @type {function} + * @private + */ + this.boundMouseMove_ = null; + this.boundMouseUp_ = null; + + this.target_.addEventListener('mousedown', + this.onMouseDown.bind(this), + false); +} + +/** + * The ids used for drag event types. + * @enum {string} + */ +tumbler.Dragger.DragEvents = { + DRAG_START: 'dragstart', // Start a drag sequence + DRAG: 'drag', // Mouse moved during a drag sequence. + DRAG_END: 'dragend' // End a drag sewquence. +}; + +/** + * Add a drag listener. Each listener should respond to thhree methods: + * handleStartDrag(), handleDrag() and handleEndDrag(). This method assumes + * that |listener| does not already exist in the array of listeners. + * @param {!Object} listener The object that will listen to drag events. + */ +tumbler.Dragger.prototype.addDragListener = function(listener) { + this.listeners_.push(listener); +} + +/** + * Handle a mousedown event: register for mousemove and mouseup, then tell + * the target that is has a DRAG_START event. + * @param {Event} event The mousedown event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseDown = function(event) { + this.boundMouseMove_ = this.onMouseMove.bind(this); + this.boundMouseUp_ = this.onMouseUp.bind(this); + this.target_.addEventListener('mousemove', this.boundMouseMove_); + this.target_.addEventListener('mouseup', this.boundMouseUp_); + this.isDragging_ = true; + var dragStartEvent = { type: tumbler.Dragger.DragEvents.DRAG_START, + clientX: event.offsetX, + clientY: event.offsetY }; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleStartDrag(this.target_, dragStartEvent); + } +} + +/** + * Handle a mousemove event: tell the target that is has a DRAG event. + * @param {Event} event The mousemove event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseMove = function(event) { + if (!this.isDragging_) + return; + var dragEvent = { type: tumbler.Dragger.DragEvents.DRAG, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleDrag(this.target_, dragEvent); + } +} + +/** + * Handle a mouseup event: un-register for mousemove and mouseup, then tell + * the target that is has a DRAG_END event. + * @param {Event} event The mouseup event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseUp = function(event) { + this.target_.removeEventListener('mouseup', this.boundMouseUp_, false); + this.target_.removeEventListener('mousemove', this.boundMouseMove_, false); + this.boundMouseUp_ = null; + this.boundMouseMove_ = null; + this.isDragging_ = false; + var dragEndEvent = { type: tumbler.Dragger.DragEvents.DRAG_END, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleEndDrag(this.target_, dragEndEvent); + } +} diff --git a/native_client_sdk/src/examples/tumbler/opengl_context.cc b/native_client_sdk/src/examples/tumbler/opengl_context.cc new file mode 100644 index 0000000..f59013b --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/opengl_context.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Native Client 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 "examples/tumbler/opengl_context.h" + +#include <pthread.h> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +namespace { +// This is called by the brower when the 3D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<tumbler::OpenGLContext*>(data)->set_flush_pending(false); +} +} // namespace + +namespace tumbler { + +OpenGLContext::OpenGLContext(pp::Instance* instance) + : pp::Graphics3DClient(instance), + flush_pending_(false) { + pp::Module* module = pp::Module::Get(); + assert(module); + gles2_interface_ = static_cast<const struct PPB_OpenGLES2*>( + module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE)); + assert(gles2_interface_); +} + +OpenGLContext::~OpenGLContext() { + glSetCurrentContextPPAPI(0); +} + +bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) { + if (instance == NULL) { + glSetCurrentContextPPAPI(0); + return false; + } + // Lazily create the Pepper context. + if (context_.is_null()) { + int32_t attribs[] = { + PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, + PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24, + PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8, + PP_GRAPHICS3DATTRIB_SAMPLES, 0, + PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0, + PP_GRAPHICS3DATTRIB_WIDTH, size_.width(), + PP_GRAPHICS3DATTRIB_HEIGHT, size_.height(), + PP_GRAPHICS3DATTRIB_NONE + }; + context_ = pp::Graphics3D(instance, pp::Graphics3D(), attribs); + if (context_.is_null()) { + glSetCurrentContextPPAPI(0); + return false; + } + instance->BindGraphics(context_); + } + glSetCurrentContextPPAPI(context_.pp_resource()); + return true; +} + +void OpenGLContext::InvalidateContext(pp::Instance* instance) { + glSetCurrentContextPPAPI(0); +} + +void OpenGLContext::ResizeContext(const pp::Size& size) { + size_ = size; + if (!context_.is_null()) { + context_.ResizeBuffers(size.width(), size.height()); + } +} + + +void OpenGLContext::FlushContext() { + if (flush_pending()) { + // A flush is pending so do nothing; just drop this flush on the floor. + return; + } + set_flush_pending(true); + context_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this)); +} +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/tumbler/opengl_context.h b/native_client_sdk/src/examples/tumbler/opengl_context.h new file mode 100644 index 0000000..b75060d --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/opengl_context.h @@ -0,0 +1,93 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + +/// +/// @file +/// OpenGLContext manages the OpenGL context in the browser that is associated +/// with a @a pp::Instance instance. +/// + +#include <assert.h> +#include <pthread.h> + +#include <algorithm> +#include <string> + +#include "examples/tumbler/opengl_context_ptrs.h" +#include "ppapi/c/ppb_opengles2.h" +#include "ppapi/cpp/graphics_3d_client.h" +#include "ppapi/cpp/graphics_3d.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/size.h" + +namespace tumbler { + +/// OpenGLContext manages an OpenGL rendering context in the browser. +/// +class OpenGLContext : public pp::Graphics3DClient { + public: + explicit OpenGLContext(pp::Instance* instance); + + /// Release all the in-browser resources used by this context, and make this + /// context invalid. + virtual ~OpenGLContext(); + + /// The Graphics3DClient interfcace. + virtual void Graphics3DContextLost() { + assert(!"Unexpectedly lost graphics context"); + } + + /// Make @a this the current 3D context in @a instance. + /// @param instance The instance of the NaCl module that will receive the + /// the current 3D context. + /// @return success. + bool MakeContextCurrent(pp::Instance* instance); + + /// Flush the contents of this context to the browser's 3D device. + void FlushContext(); + + /// Make the underlying 3D device invalid, so that any subsequent rendering + /// commands will have no effect. The next call to MakeContextCurrent() will + /// cause the underlying 3D device to get rebound and start receiving + /// receiving rendering commands again. Use InvalidateContext(), for + /// example, when resizing the context's viewing area. + void InvalidateContext(pp::Instance* instance); + + /// Resize the context. + void ResizeContext(const pp::Size& size); + + /// The OpenGL ES 2.0 interface. + const struct PPB_OpenGLES2* gles2() const { + return gles2_interface_; + } + + /// The PP_Resource needed to make GLES2 calls through the Pepper interface. + const PP_Resource gl_context() const { + return context_.pp_resource(); + } + + /// Indicate whether a flush is pending. This can only be called from the + /// main thread; it is not thread safe. + bool flush_pending() const { + return flush_pending_; + } + void set_flush_pending(bool flag) { + flush_pending_ = flag; + } + + private: + pp::Size size_; + pp::Graphics3D context_; + bool flush_pending_; + + const struct PPB_OpenGLES2* gles2_interface_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + diff --git a/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h b/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h new file mode 100644 index 0000000..3478521 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + +// A convenience wrapper for a shared OpenGLContext pointer type. As other +// smart pointer types are needed, add them here. + +#include <tr1/memory> + +namespace tumbler { + +class OpenGLContext; + +typedef std::tr1::shared_ptr<OpenGLContext> SharedOpenGLContext; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + diff --git a/native_client_sdk/src/examples/tumbler/scripting_bridge.cc b/native_client_sdk/src/examples/tumbler/scripting_bridge.cc new file mode 100644 index 0000000..e74bd9e --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/scripting_bridge.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2011 The Native Client 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 "examples/tumbler/scripting_bridge.h" + +namespace { +const char* const kWhiteSpaceCharacters = " \t"; + +// Helper function to pull out the next token in |token_string|. A token is +// delimited by whitespace. Scanning begins at |*pos|, if pos goes beyond the +// end of |token_string|, it is set to std::string::npos and an empty string +// is returned. On return, |*pos| will point to the beginning of the next +// token. |pos| must not be NULL. +const std::string ScanToken(const std::string& token_string, size_t* pos) { + std::string token; + if (*pos == std::string::npos) { + return token; + } + size_t token_start_pos = token_string.find_first_not_of(kWhiteSpaceCharacters, + *pos); + size_t token_end_pos = token_string.find_first_of(kWhiteSpaceCharacters, + token_start_pos); + if (token_start_pos != std::string::npos) { + token = token_string.substr(token_start_pos, token_end_pos); + } + *pos = token_end_pos; + return token; +} + +// Take a string of the form 'name:value' and split it into two strings, one +// containing 'name' and the other 'value'. If the ':' separator is missing, +// or is the last character in |parameter|, |parameter| is copied to +// |param_name|, |param_value| is left unchanged and false is returned. +bool ParseParameter(const std::string& parameter, + std::string* param_name, + std::string* param_value) { + bool success = false; + size_t sep_pos = parameter.find_first_of(':'); + if (sep_pos != std::string::npos) { + *param_name = parameter.substr(0, sep_pos); + if (sep_pos < parameter.length() - 1) { + *param_value = parameter.substr(sep_pos + 1); + success = true; + } else { + success = false; + } + } else { + *param_name = parameter; + success = false; + } + return success; +} +} // namespace + +namespace tumbler { + +bool ScriptingBridge::AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method) { + if (method_name.size() == 0 || method == NULL) + return false; + method_dictionary_.insert( + std::pair<std::string, SharedMethodCallbackExecutor>(method_name, + method)); + return true; +} + +bool ScriptingBridge::InvokeMethod(const std::string& method) { + size_t current_pos = 0; + const std::string method_name = ScanToken(method, ¤t_pos); + MethodDictionary::iterator method_iter; + method_iter = method_dictionary_.find(method_name); + if (method_iter != method_dictionary_.end()) { + // Pull out the method parameters and build a dictionary that maps + // parameter names to values. + std::map<std::string, std::string> param_dict; + while (current_pos != std::string::npos) { + const std::string parameter = ScanToken(method, ¤t_pos); + if (parameter.length()) { + std::string param_name; + std::string param_value; + if (ParseParameter(parameter, ¶m_name, ¶m_value)) { + // Note that duplicate parameter names will override each other. The + // last one in the method string will be used. + param_dict[param_name] = param_value; + } + } + } + (*method_iter->second).Execute(*this, param_dict); + return true; + } + return false; +} + +} // namespace tumbler diff --git a/native_client_sdk/src/examples/tumbler/scripting_bridge.h b/native_client_sdk/src/examples/tumbler/scripting_bridge.h new file mode 100644 index 0000000..3e2f73b --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/scripting_bridge.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ +#define EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ + +#include <map> +#include <string> +#include <tr1/memory> +#include <vector> + +#include "examples/tumbler/callback.h" +#include "ppapi/cpp/var.h" + +namespace tumbler { + +class MethodCallbackExecutor; + +// This class handles the interface between the browser and the NaCl module. +// There is a single point of entry from the browser: postMessage(). The +// string passed to postMessage() has this format: +// 'function_name arg_name0:arg_0 arg_name1:arg1 ...' +// The arguments have undetermined type; they are placed in a map of argument +// names and values. Values are all strings, it is up to the target code to +// do any type coercion. +// Methods called by the scripting bridge must have a signature like this: +// void Method(const ScriptingBridge& bridge, +// const ParameterDictionary&); +class ScriptingBridge { + public: + // Shared pointer type used in the method map. + typedef std::tr1::shared_ptr<MethodCallbackExecutor> + SharedMethodCallbackExecutor; + + virtual ~ScriptingBridge() {} + + // Causes |method_name| to be published as a method that can be called via + // postMessage() from the browser. Associates this method with |method|. + bool AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method); + + bool InvokeMethod(const std::string& method); + + private: + typedef std::map<std::string, SharedMethodCallbackExecutor> MethodDictionary; + + MethodDictionary method_dictionary_; +}; + +} // namespace tumbler +#endif // EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ diff --git a/native_client_sdk/src/examples/tumbler/shader_util.cc b/native_client_sdk/src/examples/tumbler/shader_util.cc new file mode 100644 index 0000000..2bbfc84 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/shader_util.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2011 The Native Client 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 "examples/tumbler/shader_util.h" + +#include <stdlib.h> +#include <stdio.h> + +namespace shader_util { + +GLuint CreateShaderOfType(GLenum type, const char *shader_src) { + GLuint shader; + GLint compiled; + + // Create the shader object + shader = glCreateShader(type); + + if (shader == 0) + return 0; + + // Load and compile the shader source + glShaderSource(shader, 1, &shader_src, NULL); + glCompileShader(shader); + + // Check the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled == 0) { + GLint info_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast<char*>(malloc(sizeof(char) * info_len)); + glGetShaderInfoLog(shader, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error compiling shader:\n%s\n", info_log); + free(info_log); + } + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src) { + GLuint vertex_shader; + GLuint fragment_shader; + GLuint program_object; + GLint linked; + + // Load the vertex/fragment shaders + vertex_shader = CreateShaderOfType(GL_VERTEX_SHADER, vertex_shader_src); + if (vertex_shader == 0) + return 0; + fragment_shader = CreateShaderOfType(GL_FRAGMENT_SHADER, fragment_shader_src); + if (fragment_shader == 0) { + glDeleteShader(vertex_shader); + return 0; + } + + // Create the program object and attach the shaders. + program_object = glCreateProgram(); + if (program_object == 0) + return 0; + glAttachShader(program_object, vertex_shader); + glAttachShader(program_object, fragment_shader); + + // Link the program + glLinkProgram(program_object); + + // Check the link status + glGetProgramiv(program_object, GL_LINK_STATUS, &linked); + if (linked == 0) { + GLint info_len = 0; + glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast<char*>(malloc(info_len)); + glGetProgramInfoLog(program_object, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error linking program:\n%s\n", info_log); + free(info_log); + } + glDeleteProgram(program_object); + return 0; + } + + // Delete these here because they are attached to the program object. + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program_object; +} + +} // namespace shader_util diff --git a/native_client_sdk/src/examples/tumbler/shader_util.h b/native_client_sdk/src/examples/tumbler/shader_util.h new file mode 100644 index 0000000..635b16b --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/shader_util.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Some simple helper functions that load shaders and create program objects. + +#ifndef EXAMPLES_TUMBLER_SHADER_UTIL_H_ +#define EXAMPLES_TUMBLER_SHADER_UTIL_H_ + +#include <GLES2/gl2.h> + +namespace shader_util { + +// Load and compile a shader. |type| can be one of GL_VERTEX_SHADER or +// GL_FRAGMENT_SHADER. Returns a non-0 value representing the compiled +// shader on success, 0 on failure. The caller is responsible for deleting +// the returned shader using glDeleteShader(). +GLuint CreateShaderOfType(GLenum type, const char *shader_src); + +// Load and compile the vertex and fragment shaders, then link these together +// into a complete program. Returns a non-0 value representing the program on, +// success or 0 on failure. The caller is responsible for deleting the +// returned program using glDeleteProgram(). +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src); + +} // namespace shader_util + +#endif // EXAMPLES_TUMBLER_SHADER_UTIL_H_ diff --git a/native_client_sdk/src/examples/tumbler/trackball.js b/native_client_sdk/src/examples/tumbler/trackball.js new file mode 100644 index 0000000..88b9a62 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/trackball.js @@ -0,0 +1,296 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Implement a virtual trackball in the tumbler.Trackball + * class. This class maps 2D mouse events to 3D rotations by simulating a + * trackball that you roll by dragging the mouse. There are two principle + * methods in the class: startAtPointInFrame which you use to begin a trackball + * simulation and rollToPoint, which you use while dragging the mouse. The + * rollToPoint method returns a rotation expressed as a quaternion. + */ + + +// Requires tumbler.Application +// Requires tumbler.DragEvent +// Requires tumbler.Vector3 + +/** + * Constructor for the Trackball object. This class maps 2D mouse drag events + * into 3D rotations by simulating a trackball. The idea is to simulate + * clicking on the trackball, and then rolling it as you drag the mouse. + * The math behind the trackball is simple: start with a vector from the first + * mouse-click on the ball to the center of the 3D view. At the same time, set + * the radius of the ball to be the smaller dimension of the 3D view. As you + * drag the mouse around in the 3D view, a second vector is computed from the + * surface of the ball to the center. The axis of rotation is the cross + * product of these two vectors, and the angle of rotation is the angle between + * the two vectors. + * @constructor + */ +tumbler.Trackball = function() { + /** + * The square of the trackball's radius. The math never looks at the radius, + * but looks at the radius squared. + * @type {number} + * @private + */ + this.sqrRadius_ = 0; + + /** + * The 3D vector representing the point on the trackball where the mouse + * was clicked. Default is pointing stright through the center of the ball. + * @type {Object} + * @private + */ + this.rollStart_ = new tumbler.Vector3(0, 0, 1); + + /** + * The 2D center of the frame that encloses the trackball. + * @type {!Object} + * @private + */ + this.center_ = { x: 0, y: 0 }; + + /** + * Cached camera orientation. When a drag START event happens this is set to + * the current orientation in the calling view's plugin. The default is the + * identity quaternion. + * @type {Array.<number>} + * @private + */ + this.cameraOrientation_ = [0, 0, 0, 1]; +}; + +/** + * Compute the dimensions of the virtual trackball to fit inside |frameSize|. + * The radius of the trackball is set to be 1/2 of the smaller of the two frame + * dimensions, the center point is at the midpoint of each side. + * @param {!goog.math.Size} frameSize 2D-point representing the size of the + * element that encloses the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.initInFrame_ = function(frameSize) { + // Compute the radius of the virtual trackball. This is 1/2 of the smaller + // of the frame's width and height. + var halfFrameSize = 0.5 * Math.min(frameSize.width, frameSize.height); + // Cache the square of the trackball's radius. + this.sqrRadius_ = halfFrameSize * halfFrameSize; + // Figure the center of the view. + this.center_.x = frameSize.width * 0.5; + this.center_.y = frameSize.height * 0.5; +}; + +/** + * Method to convert (by translation) a 2D client point from a coordinate space + * with origin in the lower-left corner of the client view to a space with + * origin in the center of the client view. Use this method before mapping the + * 2D point to he 3D tackball point (see also the projectOnTrackball_() method). + * Call the startAtPointInFrame before calling this method so that the + * |center_| property is correctly initialized. + * @param {!Object} clientPoint map this point to the coordinate space with + * origin in thecenter of the client view. + * @return {Object} the converted point. + * @private + */ +tumbler.Trackball.prototype.convertClientPoint_ = function(clientPoint) { + var difference = { x: clientPoint.x - this.center_.x, + y: clientPoint.y - this.center_.y } + return difference; +}; + +/** + * Method to map a 2D point to a 3D point on the virtual trackball that was set + * up using the startAtPointInFrame method. If the point lies outside of the + * radius of the virtual trackball, then the z-coordinate of the 3D point + * is set to 0. + * @param {!Object.<x, y>} point 2D-point in the coordinate space with origin + * in the center of the client view. + * @return {tumbler.Vector3} the 3D point on the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.projectOnTrackball_ = function(point) { + var sqrRadius2D = point.x * point.x + point.y * point.y; + var zValue; + if (sqrRadius2D > this.sqrRadius_) { + // |point| lies outside the virtual trackball's sphere, so use a virtual + // z-value of 0. This is equivalent to clicking on the horizontal equator + // of the trackball. + zValue = 0; + } else { + // A sphere can be defined as: r^2 = x^2 + y^2 + z^2, so z = + // sqrt(r^2 - (x^2 + y^2)). + zValue = Math.sqrt(this.sqrRadius_ - sqrRadius2D); + } + var trackballPoint = new tumbler.Vector3(point.x, point.y, zValue); + return trackballPoint; +}; + +/** + * Method to start up the trackball. The trackball works by pretending that a + * ball encloses the 3D view. You roll this pretend ball with the mouse. For + * example, if you click on the center of the ball and move the mouse straight + * to the right, you roll the ball around its Y-axis. This produces a Y-axis + * rotation. You can click on the "edge" of the ball and roll it around + * in a circle to get a Z-axis rotation. + * @param {!Object.<x, y>} startPoint 2D-point, usually the mouse-down + * point. + * @param {!Object.<width, height>} frameSize 2D-point representing the size of + * the element that encloses the virtual trackball. + */ +tumbler.Trackball.prototype.startAtPointInFrame = + function(startPoint, frameSize) { + this.initInFrame_(frameSize); + // Compute the starting vector from the surface of the ball to its center. + this.rollStart_ = this.projectOnTrackball_( + this.convertClientPoint_(startPoint)); +}; + +/** + * Method to roll the virtual trackball; call this in response to a mouseDrag + * event. Takes |dragPoint| and projects it from 2D mouse coordinates onto the + * virtual track ball that was set up in startAtPointInFrame method. + * Returns a quaternion that represents the rotation from |rollStart_| to + * |rollEnd_|. + * @param {!Object.<x, y>} dragPoint 2D-point representing the + * destination mouse point. + * @return {Array.<number>} a quaternion that represents the rotation from + * the point wnere the mouse was clicked on the trackball to this point. + * The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + * imaginary part of the quaternion and is computed as [x, y, z] * + * sin(angle/2). + */ +tumbler.Trackball.prototype.rollToPoint = function(dragPoint) { + var rollTo = this.convertClientPoint_(dragPoint); + if ((Math.abs(this.rollStart_.x - rollTo.x) < + tumbler.Trackball.DOUBLE_EPSILON) && + (Math.abs(this.rollStart_.y, rollTo.y) < + tumbler.Trackball.DOUBLE_EPSILON)) { + // Not enough change in the vectors to roll the ball, return the identity + // quaternion. + return [0, 0, 0, 1]; + } + + // Compute the ending vector from the surface of the ball to its center. + var rollEnd = this.projectOnTrackball_(rollTo); + + // Take the cross product of the two vectors. r = s X e + var rollVector = this.rollStart_.cross(rollEnd); + var invStartMag = 1.0 / this.rollStart_.magnitude(); + var invEndMag = 1.0 / rollEnd.magnitude(); + + // cos(a) = (s . e) / (||s|| ||e||) + var cosAng = this.rollStart_.dot(rollEnd) * invStartMag * invEndMag; + // sin(a) = ||(s X e)|| / (||s|| ||e||) + var sinAng = rollVector.magnitude() * invStartMag * invEndMag; + // Build a quaternion that represents the rotation about |rollVector|. + // Use atan2 for a better angle. If you use only cos or sin, you only get + // half the possible angles, and you can end up with rotations that flip + // around near the poles. + var rollHalfAngle = Math.atan2(sinAng, cosAng) * 0.5; + rollVector.normalize(); + // The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + // imaginary part of the quaternion and is computed as [x, y, z] * + // sin(angle/2). + rollVector.scale(Math.sin(rollHalfAngle)); + var ballQuaternion = [rollVector.x, + rollVector.y, + rollVector.z, + Math.cos(rollHalfAngle)]; + return ballQuaternion; +}; + +/** + * Handle the drag START event: grab the current camera orientation from the + * sending view and set up the virtual trackball. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragStartEvent The DRAG_START event that + * triggered this handler. + */ +tumbler.Trackball.prototype.handleStartDrag = + function(controller, dragStartEvent) { + // Cache the camera orientation. The orientations from the trackball as it + // rolls are concatenated to this orientation and pushed back into the + // plugin on the other side of the JavaScript bridge. + controller.setCameraOrientation(this.cameraOrientation_); + // Invert the y-coordinate for the trackball computations. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragStartEvent.clientX, + y: frameSize.height - dragStartEvent.clientY }; + this.startAtPointInFrame(flippedY, frameSize); +}; + +/** + * Handle the drag DRAG event: concatenate the current orientation to the + * cached orientation. Send this final value through to the GSPlugin via the + * setValueForKey() method. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEvent The DRAG event that triggered this + * handler. + */ +tumbler.Trackball.prototype.handleDrag = + function(controller, dragEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEvent.clientX, + y: frameSize.height - dragEvent.clientY }; + controller.setCameraOrientation( + tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_)); +}; + +/** + * Handle the drag END event: get the final orientation and concatenate it to + * the cached orientation. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEndEvent The DRAG_END event that triggered + * this handler. + */ +tumbler.Trackball.prototype.handleEndDrag = + function(controller, dragEndEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEndEvent.clientX, + y: frameSize.height - dragEndEvent.clientY }; + this.cameraOrientation_ = tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_); + controller.setCameraOrientation(this.cameraOrientation_); +}; + +/** + * A utility function to multiply two quaterions. Returns the product q0 * q1. + * This is effectively the same thing as concatenating the two rotations + * represented in each quaternion together. Note that quaternion multiplication + * is NOT commutative: q0 * q1 != q1 * q0. + * @param {!Array.<number>} q0 A 4-element array representing the first + * quaternion. + * @param {!Array.<number>} q1 A 4-element array representing the second + * quaternion. + * @return {Array.<number>} A 4-element array representing the product q0 * q1. + */ +tumbler.multQuaternions = function(q0, q1) { + // Return q0 * q1 (note the order). + var qMult = [ + q0[3] * q1[0] + q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1], + q0[3] * q1[1] - q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0], + q0[3] * q1[2] + q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3], + q0[3] * q1[3] - q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + ]; + return qMult; +}; + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in Closure somewhere (goog.math?). + * @type {number} + */ +tumbler.Trackball.DOUBLE_EPSILON = 1.0e-16; diff --git a/native_client_sdk/src/examples/tumbler/transforms.cc b/native_client_sdk/src/examples/tumbler/transforms.cc new file mode 100644 index 0000000..79cb9cf --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/transforms.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2011 The Native Client 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 "examples/tumbler/transforms.h" + +#include <math.h> +#include <string.h> +#include <GLES2/gl2.h> + +namespace transform_4x4 { + +static const GLfloat kPI = 3.1415926535897932384626433832795f; + +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz) { + m[12] += (m[0] * tx + m[4] * ty + m[8] * tz); + m[13] += (m[1] * tx + m[5] * ty + m[9] * tz); + m[14] += (m[2] * tx + m[6] * ty + m[10] * tz); + m[15] += (m[3] * tx + m[7] * ty + m[11] * tz); +} + +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z) { + GLfloat delta_x = right - left; + GLfloat delta_y = top - bottom; + GLfloat delta_z = far_z - near_z; + GLfloat frustum[16]; + + if ((near_z <= 0.0f) || (far_z <= 0.0f) || + (delta_x <= 0.0f) || (delta_y <= 0.0f) || (delta_z <= 0.0f)) + return; + + frustum[0] = 2.0f * near_z / delta_x; + frustum[1] = frustum[2] = frustum[3] = 0.0f; + + frustum[5] = 2.0f * near_z / delta_y; + frustum[4] = frustum[6] = frustum[7] = 0.0f; + + frustum[8] = (right + left) / delta_x; + frustum[9] = (top + bottom) / delta_y; + frustum[10] = -(near_z + far_z) / delta_z; + frustum[11] = -1.0f; + + frustum[14] = -2.0f * near_z * far_z / delta_z; + frustum[12] = frustum[13] = frustum[15] = 0.0f; + + transform_4x4::Multiply(m, frustum, m); +} + + +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z) { + GLfloat frustum_w, frustum_h; + + frustum_h = tanf((fovy * 0.5f) / 180.0f * kPI) * near_z; + frustum_w = frustum_h * aspect; + transform_4x4::Frustum(m, -frustum_w, frustum_w, -frustum_h, frustum_h, + near_z, far_z); +} + +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b) { + GLfloat tmp[16]; + // tmp = a . b + GLfloat a0, a1, a2, a3; + a0 = a[0]; + a1 = a[1]; + a2 = a[2]; + a3 = a[3]; + tmp[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[4]; + a1 = a[5]; + a2 = a[6]; + a3 = a[7]; + tmp[4] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[5] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[6] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[7] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[8]; + a1 = a[9]; + a2 = a[10]; + a3 = a[11]; + tmp[8] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[9] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[10] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[11] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[12]; + a1 = a[13]; + a2 = a[14]; + a3 = a[15]; + tmp[12] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[13] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[14] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[15] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + memcpy(m, tmp, sizeof(GLfloat) * 4 * 4); +} + +void LoadIdentity(GLfloat* m) { + memset(m, 0, sizeof(GLfloat) * 4 * 4); + m[0] = m[5] = m[10] = m[15] = 1.0f; +} + +} // namespace transform_4x4 diff --git a/native_client_sdk/src/examples/tumbler/transforms.h b/native_client_sdk/src/examples/tumbler/transforms.h new file mode 100644 index 0000000..5ac3d6e --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/transforms.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TRANSFORMS_H_ +#define EXAMPLES_TUMBLER_TRANSFORMS_H_ + +#include <GLES2/gl2.h> + +// A very simple set of 4x4 matrix routines. In all these routines, the input +// matrix is assumed to be a 4x4 of GLfloats. + +namespace transform_4x4 { + +// Pre-multply |m| with a projection transformation 4x4 matrix from a +// truncated pyramid viewing frustum. +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z); + +// Replace |m| with the 4x4 identity matrix. +void LoadIdentity(GLfloat* m); + +// |m| <- |a| . |b|. |m| can point at the same memory as either |a| or |b|. +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b); + +// Pre-multiply |m| with a single-point perspective matrix based on the viewing +// frustum whose view angle is |fovy|. +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z); + +// Pre-multiply |m| with a matrix that represents a translation by |tx|, |ty|, +// |tz|. +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz); +} // namespace transform_4x4 + +#endif // EXAMPLES_TUMBLER_TRANSFORMS_H_ + diff --git a/native_client_sdk/src/examples/tumbler/tumbler.cc b/native_client_sdk/src/examples/tumbler/tumbler.cc new file mode 100644 index 0000000..57343c4 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2011 The Native Client 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 "examples/tumbler/tumbler.h" + +#include <cstdlib> +#include <cstring> +#include <string> +#include <vector> + +#include "examples/tumbler/cube.h" +#include "examples/tumbler/opengl_context.h" +#include "examples/tumbler/scripting_bridge.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" +#include "ppapi/cpp/var.h" + +namespace { +const size_t kQuaternionElementCount = 4; +const char* const kArrayStartCharacter = "["; +const char* const kArrayEndCharacter = "]"; +const char* const kArrayDelimiter = ","; + +// Return the value of parameter named |param_name| from |parameters|. If +// |param_name| doesn't exist, then return an empty string. +std::string GetParameterNamed( + const std::string& param_name, + const tumbler::MethodParameter& parameters) { + tumbler::MethodParameter::const_iterator i = + parameters.find(param_name); + if (i == parameters.end()) { + return ""; + } + return i->second; +} + +// Convert the JSON string |array| into a vector of floats. |array| is +// expected to be a string bounded by '[' and ']', and a comma-delimited list +// of numbers. Any errors result in the return of an empty array. +std::vector<float> CreateArrayFromJSON(const std::string& json_array) { + std::vector<float> float_array; + size_t array_start_pos = json_array.find_first_of(kArrayStartCharacter); + size_t array_end_pos = json_array.find_last_of(kArrayEndCharacter); + if (array_start_pos == std::string::npos || + array_end_pos == std::string::npos) + return float_array; // Malformed JSON: missing '[' or ']'. + // Pull out the array elements. + size_t token_pos = array_start_pos + 1; + while (token_pos < array_end_pos) { + float_array.push_back(strtof(json_array.data() + token_pos, NULL)); + size_t delim_pos = json_array.find_first_of(kArrayDelimiter, token_pos); + if (delim_pos == std::string::npos) + break; + token_pos = delim_pos + 1; + } + return float_array; +} +} // namespace + +namespace tumbler { + +Tumbler::Tumbler(PP_Instance instance) + : pp::Instance(instance), + cube_(NULL) { +} + +Tumbler::~Tumbler() { + // Destroy the cube view while GL context is current. + opengl_context_->MakeContextCurrent(this); + delete cube_; +} + +bool Tumbler::Init(uint32_t /* argc */, + const char* /* argn */[], + const char* /* argv */[]) { + // Add all the methods to the scripting bridge. + ScriptingBridge::SharedMethodCallbackExecutor set_orientation_method( + new tumbler::MethodCallback<Tumbler>( + this, &Tumbler::SetCameraOrientation)); + scripting_bridge_.AddMethodNamed("setCameraOrientation", + set_orientation_method); + return true; +} + +void Tumbler::HandleMessage(const pp::Var& message) { + if (!message.is_string()) + return; + scripting_bridge_.InvokeMethod(message.AsString()); +} + +void Tumbler::DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + int cube_width = cube_ ? cube_->width() : 0; + int cube_height = cube_ ? cube_->height() : 0; + if (position.size().width() == cube_width && + position.size().height() == cube_height) + return; // Size didn't change, no need to update anything. + + if (opengl_context_ == NULL) + opengl_context_.reset(new OpenGLContext(this)); + opengl_context_->InvalidateContext(this); + opengl_context_->ResizeContext(position.size()); + if (!opengl_context_->MakeContextCurrent(this)) + return; + if (cube_ == NULL) { + cube_ = new Cube(opengl_context_); + cube_->PrepareOpenGL(); + } + cube_->Resize(position.size().width(), position.size().height()); + DrawSelf(); +} + +void Tumbler::DrawSelf() { + if (cube_ == NULL || opengl_context_ == NULL) + return; + opengl_context_->MakeContextCurrent(this); + cube_->Draw(); + opengl_context_->FlushContext(); +} + +void Tumbler::SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters) { + // |parameters| is expected to contain one object named "orientation", whose + // value is a JSON string that represents an array of four floats. + if (parameters.size() != 1 || cube_ == NULL) + return; + std::string orientation_desc = GetParameterNamed("orientation", parameters); + std::vector<float> orientation = CreateArrayFromJSON(orientation_desc); + if (orientation.size() != kQuaternionElementCount) { + return; + } + cube_->SetOrientation(orientation); + DrawSelf(); +} +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/tumbler/tumbler.h b/native_client_sdk/src/examples/tumbler/tumbler.h new file mode 100644 index 0000000..1738ec3 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.h @@ -0,0 +1,64 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TUMBLER_H_ +#define EXAMPLES_TUMBLER_TUMBLER_H_ + +#include <pthread.h> +#include <map> +#include <vector> + +#include "examples/tumbler/cube.h" +#include "examples/tumbler/opengl_context.h" +#include "examples/tumbler/opengl_context_ptrs.h" +#include "examples/tumbler/scripting_bridge.h" +#include "ppapi/cpp/instance.h" + +namespace tumbler { + +class Tumbler : public pp::Instance { + public: + explicit Tumbler(PP_Instance instance); + + // The dtor makes the 3D context current before deleting the cube view, then + // destroys the 3D context both in the module and in the browser. + virtual ~Tumbler(); + + // Called by the browser when the NaCl module is loaded and all ready to go. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called whenever the in-browser window changes size. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser to handle the postMessage() call in Javascript. + virtual void HandleMessage(const pp::Var& message); + + // Bind and publish the module's methods to JavaScript. + void InitializeMethods(ScriptingBridge* bridge); + + // Set the camera orientation to the quaternion in |args[0]|. |args| must + // have length at least 1; the first element is expeted to be an Array + // object containing 4 floating point number elements (the quaternion). + // This method is bound to the JavaScript "setCameraOrientation" method and + // is called like this: + // module.setCameraOrientation([0.0, 1.0, 0.0, 0.0]); + void SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters); + + // Called to draw the contents of the module's browser area. + void DrawSelf(); + + private: + // Browser connectivity and scripting support. + ScriptingBridge scripting_bridge_; + + SharedOpenGLContext opengl_context_; + // Wouldn't it be awesome if we had boost::scoped_ptr<>? + Cube* cube_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_TUMBLER_H_ diff --git a/native_client_sdk/src/examples/tumbler/tumbler.html b/native_client_sdk/src/examples/tumbler/tumbler.html new file mode 100644 index 0000000..a3002da --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.html @@ -0,0 +1,33 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Interactive Cube Example</title> + <script type="text/javascript"> + // Provide the tumbler namespace + tumbler = {}; + </script> + <script type="text/javascript" src="bind.js"></script> + <script type="text/javascript" src="dragger.js"></script> + <script type="text/javascript" src="tumbler.js"></script> + <script type="text/javascript" src="vector3.js"></script> + <script type="text/javascript" src="trackball.js"></script> + </head> + <body id="bodyId"> + <h1>Interactive Cube Example</h1> + <p> + The Native Client module executed in this page draws a 3D cube + and allows you to rotate it using a virtual trackball method. + </p> + <div id="tumbler_view"></div> + <script type="text/javascript"> + tumbler.application = new tumbler.Application(); + tumbler.application.run('tumbler_view'); + </script> + </body> +</HTML> diff --git a/native_client_sdk/src/examples/tumbler/tumbler.js b/native_client_sdk/src/examples/tumbler/tumbler.js new file mode 100644 index 0000000..e8e42eb --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.js @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview The tumbler Application object. This object instantiates a + * Trackball object and connects it to the element named |tumbler_content|. + * It also conditionally embeds a debuggable module or a release module into + * the |tumbler_content| element. + */ + +// Requires tumbler +// Requires tumbler.Dragger +// Requires tumbler.Trackball + +/** + * Constructor for the Application class. Use the run() method to populate + * the object with controllers and wire up the events. + * @constructor + */ +tumbler.Application = function() { + /** + * The native module for the application. This refers to the module loaded + * via the <embed> tag. + * @type {Element} + * @private + */ + this.module_ = null; + + /** + * The trackball object. + * @type {tumbler.Trackball} + * @private + */ + this.trackball_ = null; + + /** + * The mouse-drag event object. + * @type {tumbler.Dragger} + * @private + */ + this.dragger_ = null; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed when they are no longer needed. + * @type {function} + * @private + */ + this.boundModuleDidLoad_ = null; +} + +/** + * The ids used for elements in the DOM. The Tumlber Application expects these + * elements to exist. + * @enum {string} + * @private + */ +tumbler.Application.DomIds_ = { + MODULE: 'tumbler', // The <embed> element representing the NaCl module + VIEW: 'tumbler_view' // The <div> containing the NaCl element. +} + +/** + * Called by the module loading function once the module has been loaded. + * @param {?Element} nativeModule The instance of the native module. + */ +tumbler.Application.prototype.moduleDidLoad = function() { + this.module_ = document.getElementById(tumbler.Application.DomIds_.MODULE); + // Unbind the load function. + this.boundModuleDidLoad_ = null; + + /** + * Set the camera orientation property on the NaCl module. + * @param {Array.<number>} orientation A 4-element array representing the + * camera orientation as a quaternion. + */ + this.module_.setCameraOrientation = function(orientation) { + var methodString = 'setCameraOrientation ' + + 'orientation:' + + JSON.stringify(orientation); + this.postMessage(methodString); + } + + this.trackball_ = new tumbler.Trackball(); + this.dragger_ = new tumbler.Dragger(this.module_); + this.dragger_.addDragListener(this.trackball_); +} + +/** + * Asserts that cond is true; issues an alert and throws an Error otherwise. + * @param {bool} cond The condition. + * @param {String} message The error message issued if cond is false. + */ +tumbler.Application.prototype.assert = function(cond, message) { + if (!cond) { + message = "Assertion failed: " + message; + alert(message); + throw new Error(message); + } +} + +/** + * The run() method starts and 'runs' the application. The trackball object + * is allocated and all the events get wired up. + * @param {?String} opt_contentDivName The id of a DOM element in which to + * embed the Native Client module. If unspecified, defaults to + * VIEW. The DOM element must exist. + */ +tumbler.Application.prototype.run = function(opt_contentDivName) { + contentDivName = opt_contentDivName || tumbler.Application.DomIds_.VIEW; + var contentDiv = document.getElementById(contentDivName); + this.assert(contentDiv, "Missing DOM element '" + contentDivName + "'"); + + // Note that the <EMBED> element is wrapped inside a <DIV>, which has a 'load' + // event listener attached. This method is used instead of attaching the + // 'load' event listener directly to the <EMBED> element to ensure that the + // listener is active before the NaCl module 'load' event fires. + this.boundModuleDidLoad_ = this.moduleDidLoad.bind(this); + contentDiv.addEventListener('load', this.boundModuleDidLoad_, true); + + // Load the published .nexe. This includes the 'nacl' attribute which + // shows how to load multi-architecture modules. Each entry in the "nexes" + // object in the .nmf manifest file is a key-value pair: the key is the + // runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired + // NaCl module. To load the debug versions of your .nexes, set the 'nacl' + // attribute to the _dbg.nmf version of the manifest file. + contentDiv.innerHTML = '<embed id="' + + tumbler.Application.DomIds_.MODULE + '" ' + + 'src=tumbler.nmf ' + + 'type="application/x-nacl" ' + + 'width="480" height="480" />' +} diff --git a/native_client_sdk/src/examples/tumbler/tumbler_module.cc b/native_client_sdk/src/examples/tumbler/tumbler_module.cc new file mode 100644 index 0000000..932e564 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler_module.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client 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 "examples/tumbler/tumbler.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with type="application/x-nacl". +class TumberModule : public pp::Module { + public: + TumberModule() : pp::Module() {} + virtual ~TumberModule() { + glTerminatePPAPI(); + } + + /// Called by the browser when the module is first loaded and ready to run. + /// This is called once per module, not once per instance of the module on + /// the page. + virtual bool Init() { + return glInitializePPAPI(get_browser_interface()) == GL_TRUE; + } + + /// Create and return a Tumbler instance object. + /// @param[in] instance The browser-side instance. + /// @return the plugin-side instance. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new tumbler::Tumbler(instance); + } +}; + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +Module* CreateModule() { + return new TumberModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/tumbler/vector3.js b/native_client_sdk/src/examples/tumbler/vector3.js new file mode 100644 index 0000000..a79f781 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/vector3.js @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview A 3D vector class. Proviudes some utility functions on + * 3-dimentional vectors. + */ + +// Requires tumbler + +/** + * Constructor for the Vector3 object. This class contains a 3-tuple that + * represents a vector in 3D space. + * @param {?number} opt_x The x-coordinate for this vector. If null or + * undefined, the x-coordinate value is set to 0. + * @param {?number} opt_y The y-coordinate for this vector. If null or + * undefined, the y-coordinate value is set to 0. + * @param {?number} opt_z The z-coordinate for this vector. If null or + * undefined, the z-coordinate value is set to 0. + * @constructor + */ +tumbler.Vector3 = function(opt_x, opt_y, opt_z) { + /** + * The vector's 3-tuple. + * @type {number} + */ + this.x = opt_x || 0; + this.y = opt_y || 0; + this.z = opt_z || 0; +} + +/** + * Method to return the magnitude of a Vector3. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.magnitude = function() { + return Math.sqrt(this.dot(this)); +} + +/** + * Normalize the vector in-place. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.normalize = function() { + var mag = this.magnitude(); + if (mag < tumbler.Vector3.DOUBLE_EPSILON) + return 0.0; // |this| is equivalent to the 0-vector, don't normalize. + this.scale(1.0 / mag); + return mag; +} + +/** + * Scale the vector in-place by |s|. + * @param {!number} s The scale factor. + */ +tumbler.Vector3.prototype.scale = function(s) { + this.x *= s; + this.y *= s; + this.z *= s; +} + +/** + * Compute the dot product: |this| . v. + * @param {!tumbler.Vector3} v The vector to dot. + * @return {number} the result of |this| . v. + */ +tumbler.Vector3.prototype.dot = function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; +} + +/** + * Compute the cross product: |this| X v. + * @param {!tumbler.Vector3} v The vector to cross with. + * @return {tumbler.Vector3} the result of |this| X v. + */ +tumbler.Vector3.prototype.cross = function(v) { + var vCross = new tumbler.Vector3(this.y * v.z - this.z * v.y, + this.z * v.x - this.x * v.z, + this.x * v.y - this.y * v.x); + return vCross; +} + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in generally available somewhere. + * @type {number} + */ +tumbler.Vector3.DOUBLE_EPSILON = 1.0e-16; |