diff options
author | dmichael@chromium.org <dmichael@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-28 22:14:13 +0000 |
---|---|---|
committer | dmichael@chromium.org <dmichael@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-28 22:14:13 +0000 |
commit | 82fae8fe8122737f9414ff848671818b4d605a09 (patch) | |
tree | 8fd4f9da05fcf4daf3203af23a3deadc336206bd /native_client_sdk | |
parent | 12b44060cb69b3109bcee395ddaddcc1c87b9d43 (diff) | |
download | chromium_src-82fae8fe8122737f9414ff848671818b4d605a09.zip chromium_src-82fae8fe8122737f9414ff848671818b4d605a09.tar.gz chromium_src-82fae8fe8122737f9414ff848671818b4d605a09.tar.bz2 |
NaCl SDK: Add an example that uses VarArrayBuffer and file input.
BUG=
TEST=
Review URL: https://chromiumcodereview.appspot.com/9802011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@129499 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
7 files changed, 452 insertions, 5 deletions
diff --git a/native_client_sdk/src/build_tools/build_sdk.py b/native_client_sdk/src/build_tools/build_sdk.py index 3eeafd6..02d3089 100755 --- a/native_client_sdk/src/build_tools/build_sdk.py +++ b/native_client_sdk/src/build_tools/build_sdk.py @@ -343,6 +343,7 @@ def BuildToolchains(pepperdir, platform, arch, pepper_ver, toolchains): EXAMPLE_MAP = { 'newlib': [ 'debugging', + 'file_histogram', 'fullscreen_tumbler', 'gamepad', 'geturl', diff --git a/native_client_sdk/src/examples/Makefile b/native_client_sdk/src/examples/Makefile index 1c891c3..16e3547 100644 --- a/native_client_sdk/src/examples/Makefile +++ b/native_client_sdk/src/examples/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) 2012 The Native Client Authors. All rights reserved. +# Copyright (c) 2012 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. @@ -7,10 +7,10 @@ # http://www.gnu.org/software/make/manual/make.html # -PROJECTS:=dlopen fullscreen_tumbler gamepad geturl hello_world_glibc -PROJECTS+=hello_world_interactive hello_world_newlib input_events load_progress -PROJECTS+=mouselock multithreaded_input_events pi_generator pong sine_synth -PROJECTS+=tumbler websocket +PROJECTS:=dlopen file_histogram fullscreen_tumbler gamepad geturl +PROJECTS:=hello_world_glibc hello_world_interactive hello_world_newlib +PROJECTS:=input_events load_progress mouselock multithreaded_input_events +PROJECTS:=pi_generator pong sine_synth tumbler websocket # Define the default target all: diff --git a/native_client_sdk/src/examples/file_histogram/Makefile b/native_client_sdk/src/examples/file_histogram/Makefile new file mode 100644 index 0000000..da2464b --- /dev/null +++ b/native_client_sdk/src/examples/file_histogram/Makefile @@ -0,0 +1,76 @@ +# Copyright (c) 2012 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. + +# +# GNU Make based build file. For details on GNU Make see: +# http://www.gnu.org/software/make/manual/make.html +# + +# +# Project information +# +# These variables store project specific settings for the project name +# build flags, files to copy or install. In the examples it is typically +# only the list of sources and project name that will actually change and +# the rest of the makefile is boilerplate for defining build rules. +# +PROJECT:=file_histogram +LDFLAGS:=-lppapi_cpp -lppapi +CXX_SOURCES:=$(PROJECT).cc + + +# +# Get pepper directory for toolchain and includes. +# +# If PEPPER_ROOT is not set, then assume it can be found a two directories up, +# from the default example directory location. +# +THIS_MAKEFILE:=$(abspath $(lastword $(MAKEFILE_LIST))) +NACL_SDK_ROOT?=$(abspath $(dir $(THIS_MAKEFILE))../..) + +# Project Build flags +WARNINGS:=-Wno-long-long -Wall -Wswitch-enum -pedantic -Werror +CXXFLAGS:=-pthread -std=gnu++98 $(WARNINGS) + +# +# Compute tool paths +# +# +OSNAME:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py) +TC_PATH:=$(abspath $(NACL_SDK_ROOT)/toolchain/$(OSNAME)_x86_newlib) +CXX:=$(TC_PATH)/bin/i686-nacl-g++ + +# +# Disable DOS PATH warning when using Cygwin based tools Windows +# +CYGWIN ?= nodosfilewarning +export CYGWIN + + +# Declare the ALL target first, to make the 'all' target the default build +all: $(PROJECT)_x86_32.nexe $(PROJECT)_x86_64.nexe + +# Define 32 bit compile and link rules for C++ sources +x86_32_OBJS:=$(patsubst %.cc,%_32.o,$(CXX_SOURCES)) +$(x86_32_OBJS) : %_32.o : %.cc $(THIS_MAKE) + $(CXX) -o $@ -c $< -m32 -O0 -g $(CXXFLAGS) + +$(PROJECT)_x86_32.nexe : $(x86_32_OBJS) + $(CXX) -o $@ $^ -m32 -O0 -g $(CXXFLAGS) $(LDFLAGS) + +# Define 64 bit compile and link rules for C++ sources +x86_64_OBJS:=$(patsubst %.cc,%_64.o,$(CXX_SOURCES)) +$(x86_64_OBJS) : %_64.o : %.cc $(THIS_MAKE) + $(CXX) -o $@ -c $< -m64 -O0 -g $(CXXFLAGS) + +$(PROJECT)_x86_64.nexe : $(x86_64_OBJS) + $(CXX) -o $@ $^ -m64 -O0 -g $(CXXFLAGS) $(LDFLAGS) + + +# Define a phony rule so it always runs, to build nexe and start up server. +.PHONY: RUN +RUN: all + python ../httpd.py + + diff --git a/native_client_sdk/src/examples/file_histogram/file_histogram.cc b/native_client_sdk/src/examples/file_histogram/file_histogram.cc new file mode 100644 index 0000000..30405f4 --- /dev/null +++ b/native_client_sdk/src/examples/file_histogram/file_histogram.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2012 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. + +/// @file file_histogram.cc +/// 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 than calls the CreateInstance() +/// method on the object returned by CreateModule(). It calls CreateInstance() +/// each time it encounters an <embed> tag that references your NaCl module. +/// +/// The browser can talk to your NaCl module via the postMessage() Javascript +/// function. When you call postMessage() on your NaCl module from the browser, +/// this becomes a call to the HandleMessage() method of your pp::Instance +/// subclass. You can send messages back to the browser by calling the +/// PostMessage() method on your pp::Instance. Note that these two methods +/// (postMessage() in Javascript and PostMessage() in C++) are asynchronous. +/// This means they return immediately - there is no waiting for the message +/// to be handled. This has implications in your program design, particularly +/// when mutating property values that are exposed to both the browser and the +/// NaCl module. + +#include <algorithm> +#include <deque> +#include <string> + +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" +#include "ppapi/cpp/var_array_buffer.h" +#include "ppapi/utility/completion_callback_factory.h" + +namespace { + +const uint32_t kBlue = 0xff4040ffu; +const uint32_t kBlack = 0xff000000u; +const size_t kHistogramSize = 256u; + +} // 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 occurence of the <embed> tag that has these +/// attributes: +/// type="application/x-nacl" +/// src="file_histogram.nmf" +class FileHistogramInstance : public pp::Instance { + public: + /// The constructor creates the plugin-side instance. + /// @param[in] instance the handle to the browser-side plugin instance. + explicit FileHistogramInstance(PP_Instance instance) + : pp::Instance(instance), + callback_factory_(this), + flushing_(false), + histogram_() { + } + virtual ~FileHistogramInstance() { + } + + private: + /// Handler for messages coming in from the browser via postMessage(). The + /// @a var_message can contain anything: a JSON string; a string that encodes + /// method names and arguments; etc. + /// + /// In this case, we only handle <code>pp::VarArrayBuffer</code>s. When we + /// receive one, we compute and display a histogram based on its contents. + /// + /// @param[in] var_message The message posted by the browser. + virtual void HandleMessage(const pp::Var& var_message) { + if (var_message.is_array_buffer()) { + pp::VarArrayBuffer buffer(var_message); + ComputeHistogram(buffer); + DrawHistogram(); + } + } + + /// Create and return a blank (all-black) <code>pp::ImageData</code> of the + /// given <code>size</code>. + pp::ImageData MakeBlankImageData(const pp::Size& size) { + const bool init_to_zero = false; + pp::ImageData image_data = pp::ImageData(this, + PP_IMAGEDATAFORMAT_BGRA_PREMUL, + size, + init_to_zero); + uint32_t* image_buffer = static_cast<uint32_t*>(image_data.data()); + for (int i = 0; i < size.GetArea(); ++i) + image_buffer[i] = kBlack; + return image_data; + } + + /// Draw a bar of the appropriate height based on <code>value</code> at + /// <code>column</code> in <code>image_data</code>. <code>value</code> must be + /// in the range [0, 1]. + void DrawBar(uint32_t column, double value, pp::ImageData* image_data) { + assert((value >= 0.0) && (value <= 1.0)); + uint32_t* image_buffer = static_cast<uint32_t*>(image_data->data()); + const uint32_t image_height = image_data->size().height(); + const uint32_t image_width = image_data->size().width(); + assert(column < image_width); + int bar_height = static_cast<int>(value * image_height); + for (int i = 0; i < bar_height; ++i) { + uint32_t row = image_height - 1 - i; + image_buffer[row * image_width + column] = kBlue; + } + } + + void PaintAndFlush(pp::ImageData* image_data) { + assert(!flushing_); + graphics_2d_context_.ReplaceContents(image_data); + graphics_2d_context_.Flush( + callback_factory_.NewCallback(&FileHistogramInstance::DidFlush)); + flushing_ = true; + } + + /// The callback that gets invoked when a flush completes. This is bound to a + /// <code>CompletionCallback</code> and passed as a parameter to + /// <code>Flush</code>. + void DidFlush(int32_t error_code) { + flushing_ = false; + // If there are no images in the queue, we're done for now. + if (paint_queue_.empty()) + return; + // Otherwise, pop the next image off the queue and draw it. + pp::ImageData image_data = paint_queue_.front(); + paint_queue_.pop_front(); + PaintAndFlush(&image_data); + } + + virtual void DidChangeView(const pp::View& view) { + if (size_ != view.GetRect().size()) { + size_ = view.GetRect().size(); + const bool is_always_opaque = true; + graphics_2d_context_ = pp::Graphics2D(this, view.GetRect().size(), + is_always_opaque); + BindGraphics(graphics_2d_context_); + // The images in our queue are the wrong size, so we won't paint them. + // We'll only draw the most recently computed histogram. + paint_queue_.clear(); + DrawHistogram(); + } + } + + /// Compute and normalize a histogram based on the given VarArrayBuffer. + void ComputeHistogram(pp::VarArrayBuffer& buffer) { + std::fill_n(histogram_, kHistogramSize, 0.0); + uint32_t buffer_size = buffer.ByteLength(); + if (buffer_size == 0) + return; + uint8_t* buffer_data = static_cast<uint8_t*>(buffer.Map()); + for (uint32_t i = 0; i < buffer_size; ++i) + histogram_[buffer_data[i]] += 1.0; + // Normalize. + double max = *std::max_element(histogram_, histogram_ + kHistogramSize); + for (uint32_t i = 0; i < kHistogramSize; ++i) + histogram_[i] /= max; + } + + /// Draw the current histogram_ in to an pp::ImageData, then paint and flush + /// that image. If we're already waiting on a flush, push it on to + /// <code>paint_queue_</code> to paint later. + void DrawHistogram() { + pp::ImageData image_data = MakeBlankImageData(size_); + for (int i = 0; + i < std::min(static_cast<int>(kHistogramSize), + image_data.size().width()); + ++i) { + DrawBar(i, histogram_[i], &image_data); + } + + if (!flushing_) + PaintAndFlush(&image_data); + else + paint_queue_.push_back(image_data); + } + + pp::Graphics2D graphics_2d_context_; + pp::CompletionCallbackFactory<FileHistogramInstance> callback_factory_; + + /// A queue of images to paint. We must maintain a queue because we can not + /// call pp::Graphics2D::Flush while a Flush is already pending. + std::deque<pp::ImageData> paint_queue_; + + /// The size of our rectangle in the DOM, as of the last time DidChangeView + /// was called. + pp::Size size_; + + /// true iff we are flushing. + bool flushing_; + + /// Stores the most recent histogram so that we can re-draw it if we get + /// resized. + double histogram_[kHistogramSize]; +}; + +/// 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 FileHistogramModule : public pp::Module { + public: + FileHistogramModule() : pp::Module() {} + virtual ~FileHistogramModule() {} + + /// Create and return a FileHistogramInstance object. + /// @param[in] instance The browser-side instance. + /// @return the plugin-side instance. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new FileHistogramInstance(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 FileHistogramModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/file_histogram/file_histogram.html b/native_client_sdk/src/examples/file_histogram/file_histogram.html new file mode 100644 index 0000000..23c523b --- /dev/null +++ b/native_client_sdk/src/examples/file_histogram/file_histogram.html @@ -0,0 +1,132 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2012 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>File Histogram</title> + + <script type="text/javascript"> + FileHistogramModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate load success. + function moduleDidLoad() { + FileHistogramModule = document.getElementById('file_histogram'); + 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 (FileHistogramModule == 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(); + } + } + + // 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('status_field'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + + function postFileContents(file) { + var reader = new FileReader(); + reader.onload = function(load_event) { + if (FileHistogramModule) + FileHistogramModule.postMessage(load_event.target.result); + } + reader.readAsArrayBuffer(file); + } + + // Handle a file being dropped on to the plugin's rectangle. + function handleFileDrop(dropEvent) { + if (!dropEvent.dataTransfer || !dropEvent.dataTransfer.files) + return; + dropEvent.stopPropagation(); + dropEvent.preventDefault(); + var files = dropEvent.dataTransfer.files; + for(var i = 0; i < files.length; ++i) + postFileContents(files[i]); + } + + // Handle a file being chosen from the <input type=file...> tag. + function handleFileInput() { + var file_input = document.getElementById("FileInput"); + var files = file_input.files; + for(var i = 0; i < files.length; ++i) + postFileContents(files[i]); + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>File Histogram example</h1> +This example demonstrates the use of VarArrayBuffer, as well as a way to allow +the user to provide one or more files to your Native Client application. +<p> +Select one or more files by clicking "Choose Files", or Drag and drop one or +more files on to the square below. The embedded NaCl instance will read each +file in order and display a histogram representing its contents. +<p> + <!-- 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 + instruction set architecture ('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. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'drop' 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. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener'); + listener.addEventListener('load', moduleDidLoad, true); + // The "drop" event fires when the user drops something (such as a file) + // on to the target element (in this case, listener). + listener.addEventListener('drop', handleFileDrop, true); + </script> + + <embed name="nacl_module" + id="file_histogram" + width=256 height=256 + src="file_histogram.nmf" + type="application/x-nacl" /> + <form name="FileInput" + action="" + method="" + onsubmit="handleFileInput()"> + <input type="file" + id="FileInput" + onchange="this.form.onsubmit()" + multiple> + </form> + </div> +</p> + +<h2>Status</h2> +<div id="status_field">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/file_histogram/file_histogram.nmf b/native_client_sdk/src/examples/file_histogram/file_histogram.nmf new file mode 100644 index 0000000..75bf0d9 --- /dev/null +++ b/native_client_sdk/src/examples/file_histogram/file_histogram.nmf @@ -0,0 +1,6 @@ +{ + "program": { + "x86-64": {"url": "file_histogram_x86_64.nexe"}, + "x86-32": {"url": "file_histogram_x86_32.nexe"} + } +} diff --git a/native_client_sdk/src/examples/index.html b/native_client_sdk/src/examples/index.html index 470d903..34aa471 100644 --- a/native_client_sdk/src/examples/index.html +++ b/native_client_sdk/src/examples/index.html @@ -174,6 +174,12 @@ mulithreading...</p></dd> send the message to the server and retrieve the reply. <p>Teaching focus: Websockets</p> </dd> + <dt><a href="file_histogram/file_histogram.html">Get URL</a></dt> + <dd> The File Histogram example demonstrates prompting the user for a file, + passing the file contents to NativeClient as a VarArrayBuffer, then drawing a + histogram representing the contents of the file to a 2D square. + <p>Teaching focus: VarArrayBuffer, 2D, File input.</p> + </dd> </dl> </body> </html> |