summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-25 22:19:36 +0000
committerbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-25 22:19:36 +0000
commitbc6cc6f9b04138e9fd09f00a9d00633734189af9 (patch)
treef8c5db06ac240e3d8573cf6deb6e618f7fca632f
parent4acc1772033deb97cad3e0edef84cf063fac5cc1 (diff)
downloadchromium_src-bc6cc6f9b04138e9fd09f00a9d00633734189af9.zip
chromium_src-bc6cc6f9b04138e9fd09f00a9d00633734189af9.tar.gz
chromium_src-bc6cc6f9b04138e9fd09f00a9d00633734189af9.tar.bz2
[NaCl SDK] Add voronoi app-engine demo.
* Modified source to allow arbitrary-sized canvas. BUG=none R=nfullagar@chromium.org, sbc@chromium.org Review URL: https://codereview.chromium.org/45193002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@231133 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--native_client_sdk/src/gonacl_appengine/src/Makefile15
-rw-r--r--native_client_sdk/src/gonacl_appengine/src/voronoi/Makefile30
-rw-r--r--native_client_sdk/src/gonacl_appengine/src/voronoi/voronoi.cc499
-rw-r--r--native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/example.js233
-rw-r--r--native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/index.html80
5 files changed, 856 insertions, 1 deletions
diff --git a/native_client_sdk/src/gonacl_appengine/src/Makefile b/native_client_sdk/src/gonacl_appengine/src/Makefile
index 7756d19..16449fe 100644
--- a/native_client_sdk/src/gonacl_appengine/src/Makefile
+++ b/native_client_sdk/src/gonacl_appengine/src/Makefile
@@ -53,7 +53,7 @@ all:
#
# All projects built by this Makefile
#
-PROJECTS = earth bullet
+PROJECTS = earth voronoi bullet
GS_URL_CONTINUOUS = gs://gonacl/demos/continuous
GS_URL_PUBLISH = gs://gonacl/demos/publish
@@ -102,6 +102,19 @@ bullet_TGTS = $(bullet_TGT_DIR)/NaClAMBullet.pexe \
$(bullet_TGTS): $(bullet_SRCS)
$(CURDIR)/bullet/build.sh
+#
+# Voronoi
+#
+voronoi_SRC_DIR = $(CURDIR)/voronoi
+voronoi_TGT_DIR = $(CURDIR)/voronoi/pnacl/Release
+voronoi_SRCS = $(voronoi_SRC_DIR)/voronoi.cc \
+ $(voronoi_SRC_DIR)/Makefile
+voronoi_TGTS = $(voronoi_TGT_DIR)/voronoi.pexe \
+ $(voronoi_TGT_DIR)/voronoi.nmf
+
+$(voronoi_TGTS): $(voronoi_SRCS)
+ $(MAKE) -C $(CURDIR)/voronoi TOOLCHAIN=pnacl CONFIG=Release
+
###############################################################################
SHELL = /bin/bash
diff --git a/native_client_sdk/src/gonacl_appengine/src/voronoi/Makefile b/native_client_sdk/src/gonacl_appengine/src/voronoi/Makefile
new file mode 100644
index 0000000..089bf90
--- /dev/null
+++ b/native_client_sdk/src/gonacl_appengine/src/voronoi/Makefile
@@ -0,0 +1,30 @@
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# GNU Makefile based on shared rules provided by the Native Client SDK.
+# See README.Makefiles for more details.
+
+VALID_TOOLCHAINS := newlib glibc pnacl
+
+NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../../..)
+include $(NACL_SDK_ROOT)/tools/common.mk
+
+TARGET = voronoi
+LIBS = $(DEPS) ppapi_simple nacl_io sdk_util ppapi_cpp ppapi pthread
+
+CFLAGS = -Wall
+SOURCES = voronoi.cc
+
+# Build rules generated by macros from common.mk:
+
+$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))
+
+ifeq ($(CONFIG),Release)
+$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
+$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
+else
+$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
+endif
+
+$(eval $(call NMF_RULE,$(TARGET),))
diff --git a/native_client_sdk/src/gonacl_appengine/src/voronoi/voronoi.cc b/native_client_sdk/src/gonacl_appengine/src/voronoi/voronoi.cc
new file mode 100644
index 0000000..1e30047
--- /dev/null
+++ b/native_client_sdk/src/gonacl_appengine/src/voronoi/voronoi.cc
@@ -0,0 +1,499 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <assert.h>
+#include <math.h>
+#include <ppapi/c/ppb_input_event.h>
+#include <ppapi/cpp/input_event.h>
+#include <ppapi/cpp/var.h>
+#include <ppapi/cpp/var_array.h>
+#include <ppapi/cpp/var_array_buffer.h>
+#include <ppapi/cpp/var_dictionary.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+
+#include "ppapi_simple/ps.h"
+#include "ppapi_simple/ps_context_2d.h"
+#include "ppapi_simple/ps_event.h"
+#include "ppapi_simple/ps_interface.h"
+#include "ppapi_simple/ps_main.h"
+#include "sdk_util/thread_pool.h"
+
+using namespace sdk_util; // For sdk_util::ThreadPool
+
+// Global properties used to setup Voronoi demo.
+namespace {
+const int kMinRectSize = 4;
+const int kStartRecurseSize = 32; // must be power-of-two
+const float kHugeZ = 1.0e38f;
+const float kPI = M_PI;
+const float kTwoPI = kPI * 2.0f;
+const unsigned int kRandomStartSeed = 0xC0DE533D;
+const int kMaxPointCount = 1024;
+const int kStartPointCount = 48;
+const int kDefaultNumRegions = 256;
+
+unsigned int g_rand_state = kRandomStartSeed;
+
+// random number helper
+inline unsigned char rand255() {
+ return static_cast<unsigned char>(rand_r(&g_rand_state) & 255);
+}
+
+// random number helper
+inline float frand() {
+ return (static_cast<float>(rand_r(&g_rand_state)) /
+ static_cast<float>(RAND_MAX));
+}
+
+// reset random seed
+inline void rand_reset(unsigned int seed) {
+ g_rand_state = seed;
+}
+
+inline uint32_t next_pow2(uint32_t x) {
+ // Via Hacker's Delight, section 3.2 "Rounding Up/Down to the Next Power of 2"
+ --x;
+ x |= (x >> 1);
+ x |= (x >> 2);
+ x |= (x >> 4);
+ x |= (x >> 8);
+ x |= (x >> 16);
+ return x + 1;
+}
+
+// BGRA helper function, for constructing a pixel for a BGRA buffer.
+inline uint32_t MakeBGRA(uint32_t b, uint32_t g, uint32_t r, uint32_t a) {
+ return (((a) << 24) | ((r) << 16) | ((g) << 8) | (b));
+}
+} // namespace
+
+// Vec2, simple 2D vector
+struct Vec2 {
+ float x, y;
+ Vec2() {}
+ Vec2(float px, float py) {
+ x = px;
+ y = py;
+ }
+ void Set(float px, float py) {
+ x = px;
+ y = py;
+ }
+};
+
+// The main object that runs Voronoi simulation.
+class Voronoi {
+ public:
+ Voronoi();
+ virtual ~Voronoi();
+ // Runs a tick of the simulations, update 2D output.
+ void Update();
+ // Handle event from user, or message from JS.
+ void HandleEvent(PSEvent* ps_event);
+
+ private:
+ // Methods prefixed with 'w' are run on worker threads.
+ uint32_t* wGetAddr(int x, int y);
+ int wCell(float x, float y);
+ inline void wFillSpan(uint32_t *pixels, uint32_t color, int width);
+ void wRenderTile(int x, int y, int w, int h);
+ void wProcessTile(int x, int y, int w, int h);
+ void wSubdivide(int x, int y, int w, int h);
+ void wMakeRect(int region, int *x, int *y, int *w, int *h);
+ bool wTestRect(int *m, int x, int y, int w, int h);
+ void wFillRect(int x, int y, int w, int h, uint32_t color);
+ void wRenderRect(int x0, int y0, int x1, int y1);
+ void wRenderRegion(int region);
+ static void wRenderRegionEntry(int region, void *thiz);
+
+ // These methods are only called by the main thread.
+ void Reset();
+ void UpdateSim();
+ void RenderDot(float x, float y, uint32_t color1, uint32_t color2);
+ void SuperimposePositions();
+ void Render();
+ void Draw();
+ // Helper to post small update messages to JS.
+ void PostUpdateMessage(const char* message_name, double value);
+
+ PSContext2D_t* ps_context_;
+ Vec2 positions_[kMaxPointCount];
+ Vec2 screen_positions_[kMaxPointCount];
+ Vec2 velocities_[kMaxPointCount];
+ uint32_t colors_[kMaxPointCount];
+ float ang_;
+ const int num_regions_;
+ int num_threads_;
+ int point_count_;
+ bool draw_points_;
+ bool draw_interiors_;
+ ThreadPool* workers_;
+};
+
+
+void Voronoi::Reset() {
+ rand_reset(kRandomStartSeed);
+ ang_ = 0.0f;
+ for (int i = 0; i < kMaxPointCount; i++) {
+ // random initial start position
+ const float x = frand();
+ const float y = frand();
+ positions_[i].Set(x, y);
+ // random directional velocity ( -1..1, -1..1 )
+ const float speed = 0.0005f;
+ const float u = (frand() * 2.0f - 1.0f) * speed;
+ const float v = (frand() * 2.0f - 1.0f) * speed;
+ velocities_[i].Set(u, v);
+ // 'unique' color (well... unique enough for our purposes)
+ colors_[i] = MakeBGRA(rand255(), rand255(), rand255(), 255);
+ }
+}
+
+Voronoi::Voronoi() : num_regions_(kDefaultNumRegions), num_threads_(0),
+ point_count_(kStartPointCount), draw_points_(true), draw_interiors_(true) {
+ Reset();
+ // By default, render from the dispatch thread.
+ workers_ = new ThreadPool(num_threads_);
+ PSEventSetFilter(PSE_ALL);
+ ps_context_ = PSContext2DAllocate(PP_IMAGEDATAFORMAT_BGRA_PREMUL);
+}
+
+Voronoi::~Voronoi() {
+ delete workers_;
+ PSContext2DFree(ps_context_);
+}
+
+inline uint32_t* Voronoi::wGetAddr(int x, int y) {
+ return ps_context_->data + x + y * ps_context_->stride / sizeof(uint32_t);
+}
+
+// This is the core of the Voronoi calculation. At a given point on the
+// screen, iterate through all voronoi positions and render them as 3D cones.
+// We're looking for the voronoi cell that generates the closest z value.
+// (not really cones - since it is all relative, we avoid doing the
+// expensive sqrt and test against z*z instead)
+// If multithreading, this function is only called by the worker threads.
+int Voronoi::wCell(float x, float y) {
+ int closest_cell = 0;
+ float zz = kHugeZ;
+ Vec2* pos = screen_positions_;
+ for (int i = 0; i < point_count_; ++i) {
+ // measured 5.18 cycles per iteration on a core2
+ float dx = x - pos[i].x;
+ float dy = y - pos[i].y;
+ float dd = (dx * dx + dy * dy);
+ if (dd < zz) {
+ zz = dd;
+ closest_cell = i;
+ }
+ }
+ return closest_cell;
+}
+
+// Given a region r, derive a non-overlapping rectangle for a thread to
+// work on.
+// If multithreading, this function is only called by the worker threads.
+void Voronoi::wMakeRect(int r, int* x, int* y, int* w, int* h) {
+ const int parts = 16;
+ assert(parts * parts == num_regions_);
+ // Round up to the nearest power of two so we can divide by parts cleanly. We
+ // could round up to the nearest 16 pixels, but it runs much faster when
+ // subdividing power-of-two squares.
+ //
+ // Many of these squares are outside of the canvas, but they will be
+ // trivially culled by wRenderRect.
+ *w = static_cast<int>(next_pow2(ps_context_->width)) / parts;
+ *h = static_cast<int>(next_pow2(ps_context_->height)) / parts;
+ *x = *w * (r % parts);
+ *y = *h * ((r / parts) % parts);
+}
+
+// Test 4 corners of a rectangle to see if they all belong to the same
+// voronoi cell. Each test is expensive so bail asap. Returns true
+// if all 4 corners match.
+// If multithreading, this function is only called by the worker threads.
+bool Voronoi::wTestRect(int* m, int x, int y, int w, int h) {
+ // each test is expensive, so exit ASAP
+ const int m0 = wCell(x, y);
+ const int m1 = wCell(x + w - 1, y);
+ if (m0 != m1) return false;
+ const int m2 = wCell(x, y + h - 1);
+ if (m0 != m2) return false;
+ const int m3 = wCell(x + w - 1, y + h - 1);
+ if (m0 != m3) return false;
+ // all 4 corners belong to the same cell
+ *m = m0;
+ return true;
+}
+
+// Quickly fill a span of pixels with a solid color.
+// If multithreading, this function is only called by the worker threads.
+inline void Voronoi::wFillSpan(uint32_t* pixels, uint32_t color, int width) {
+ if (!draw_interiors_) {
+ const uint32_t gray = MakeBGRA(128, 128, 128, 255);
+ color = gray;
+ }
+
+ for (int i = 0; i < width; ++i)
+ *pixels++ = color;
+}
+
+// Quickly fill a rectangle with a solid color.
+// If multithreading, this function is only called by the worker threads.
+void Voronoi::wFillRect(int x, int y, int w, int h, uint32_t color) {
+ const uint32_t stride_in_pixels = ps_context_->stride / sizeof(uint32_t);
+ uint32_t* pixels = wGetAddr(x, y);
+ for (int j = 0; j < h; j++) {
+ wFillSpan(pixels, color, w);
+ pixels += stride_in_pixels;
+ }
+}
+
+// When recursive subdivision reaches a certain minimum without finding a
+// rectangle that has four matching corners belonging to the same voronoi
+// cell, this function will break the retangular 'tile' into smaller scanlines
+// and look for opportunities to quick fill at the scanline level. If the
+// scanline can't be quick filled, it will slow down even further and compute
+// voronoi membership per pixel.
+void Voronoi::wRenderTile(int x, int y, int w, int h) {
+ // rip through a tile
+ const uint32_t stride_in_pixels = ps_context_->stride / sizeof(uint32_t);
+ uint32_t* pixels = wGetAddr(x, y);
+ for (int j = 0; j < h; j++) {
+ // get start and end cell values
+ int ms = wCell(x + 0, y + j);
+ int me = wCell(x + w - 1, y + j);
+ // if the end points are the same, quick fill the span
+ if (ms == me) {
+ wFillSpan(pixels, colors_[ms], w);
+ } else {
+ // else compute each pixel in the span... this is the slow part!
+ uint32_t* p = pixels;
+ *p++ = colors_[ms];
+ for (int i = 1; i < (w - 1); i++) {
+ int m = wCell(x + i, y + j);
+ *p++ = colors_[m];
+ }
+ *p++ = colors_[me];
+ }
+ pixels += stride_in_pixels;
+ }
+}
+
+// Take a rectangular region and do one of -
+// If all four corners below to the same voronoi cell, stop recursion and
+// quick fill the rectangle.
+// If the minimum rectangle size has been reached, break out of recursion
+// and process the rectangle. This small rectangle is called a tile.
+// Otherwise, keep recursively subdividing the rectangle into 4 equally
+// sized smaller rectangles.
+// If multithreading, this function is only called by the worker threads.
+void Voronoi::wSubdivide(int x, int y, int w, int h) {
+ int m;
+ // if all 4 corners are equal, quick fill interior
+ if (wTestRect(&m, x, y, w, h)) {
+ wFillRect(x, y, w, h, colors_[m]);
+ } else {
+ // did we reach the minimum rectangle size?
+ if ((w <= kMinRectSize) || (h <= kMinRectSize)) {
+ wRenderTile(x, y, w, h);
+ } else {
+ // else recurse into smaller rectangles
+ const int half_w = w / 2;
+ const int half_h = h / 2;
+ wSubdivide(x, y, half_w, half_h);
+ wSubdivide(x + half_w, y, w - half_w, half_h);
+ wSubdivide(x, y + half_h, half_w, h - half_h);
+ wSubdivide(x + half_w, y + half_h, w - half_w, h - half_h);
+ }
+ }
+}
+
+// This function cuts up the rectangle into squares (preferably power-of-two).
+// If multithreading, this function is only called by the worker threads.
+void Voronoi::wRenderRect(int x, int y, int w, int h) {
+ for (int iy = y; iy < (y + h); iy += kStartRecurseSize) {
+ for (int ix = x; ix < (x + w); ix += kStartRecurseSize) {
+ int iw = kStartRecurseSize;
+ int ih = kStartRecurseSize;
+ // Clamp width + height.
+ if (ix + iw > ps_context_->width)
+ iw = ps_context_->width - ix;
+ if (iy + ih > ps_context_->height)
+ ih = ps_context_->height - iy;
+ if (iw <= 0 || ih <= 0)
+ continue;
+
+ wSubdivide(ix, iy, iw, ih);
+ }
+ }
+}
+
+// If multithreading, this function is only called by the worker threads.
+void Voronoi::wRenderRegion(int region) {
+ // convert region # into x0, y0, x1, y1 rectangle
+ int x, y, w, h;
+ wMakeRect(region, &x, &y, &w, &h);
+ // render this rectangle
+ wRenderRect(x, y, w, h);
+}
+
+// Entry point for worker thread. Can't pass a member function around, so we
+// have to do this little round-about.
+void Voronoi::wRenderRegionEntry(int region, void* thiz) {
+ static_cast<Voronoi*>(thiz)->wRenderRegion(region);
+}
+
+// Function Voronoi::UpdateSim()
+// Run a simple sim to move the voronoi positions. This update loop
+// is run once per frame. Called from the main thread only, and only
+// when the worker threads are idle.
+void Voronoi::UpdateSim() {
+ ang_ += 0.002f;
+ if (ang_ > kTwoPI) {
+ ang_ = ang_ - kTwoPI;
+ }
+ float z = cosf(ang_) * 3.0f;
+ // push the points around on the screen for animation
+ for (int j = 0; j < kMaxPointCount; j++) {
+ positions_[j].x += (velocities_[j].x) * z;
+ positions_[j].y += (velocities_[j].y) * z;
+ screen_positions_[j].x = positions_[j].x * ps_context_->width;
+ screen_positions_[j].y = positions_[j].y * ps_context_->height;
+ }
+}
+
+// Renders a small diamond shaped dot at x, y clipped against the window
+void Voronoi::RenderDot(float x, float y, uint32_t color1, uint32_t color2) {
+ const int ix = static_cast<int>(x);
+ const int iy = static_cast<int>(y);
+ const uint32_t stride_in_pixels = ps_context_->stride / sizeof(uint32_t);
+ // clip it against window
+ if (ix < 1) return;
+ if (ix >= (ps_context_->width - 1)) return;
+ if (iy < 1) return;
+ if (iy >= (ps_context_->height - 1)) return;
+ uint32_t* pixel = wGetAddr(ix, iy);
+ // render dot as a small diamond
+ *pixel = color1;
+ *(pixel - 1) = color2;
+ *(pixel + 1) = color2;
+ *(pixel - stride_in_pixels) = color2;
+ *(pixel + stride_in_pixels) = color2;
+}
+
+// Superimposes dots on the positions.
+void Voronoi::SuperimposePositions() {
+ const uint32_t white = MakeBGRA(255, 255, 255, 255);
+ const uint32_t gray = MakeBGRA(192, 192, 192, 255);
+ for (int i = 0; i < point_count_; i++) {
+ RenderDot(
+ screen_positions_[i].x, screen_positions_[i].y, white, gray);
+ }
+}
+
+// Renders the Voronoi diagram, dispatching the work to multiple threads.
+void Voronoi::Render() {
+ workers_->Dispatch(num_regions_, wRenderRegionEntry, this);
+ if (draw_points_)
+ SuperimposePositions();
+}
+
+// Handle input events from the user and messages from JS.
+void Voronoi::HandleEvent(PSEvent* ps_event) {
+ // Give the 2D context a chance to process the event.
+ if (0 != PSContext2DHandleEvent(ps_context_, ps_event))
+ return;
+ if (ps_event->type == PSE_INSTANCE_HANDLEINPUT) {
+ // Convert Pepper Simple event to a PPAPI C++ event
+ pp::InputEvent event(ps_event->as_resource);
+ switch (event.GetType()) {
+ case PP_INPUTEVENT_TYPE_TOUCHSTART:
+ case PP_INPUTEVENT_TYPE_TOUCHMOVE: {
+ pp::TouchInputEvent touches = pp::TouchInputEvent(event);
+ uint32_t count = touches.GetTouchCount(PP_TOUCHLIST_TYPE_TOUCHES);
+ // Touch points 0..n directly set position of points 0..n in
+ // Voronoi diagram.
+ for (uint32_t i = 0; i < count; i++) {
+ pp::TouchPoint touch =
+ touches.GetTouchByIndex(PP_TOUCHLIST_TYPE_TOUCHES, i);
+ pp::FloatPoint point = touch.position();
+ positions_[i].Set(point.x() / ps_context_->width,
+ point.y() / ps_context_->height);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ } else if (ps_event->type == PSE_INSTANCE_HANDLEMESSAGE) {
+ // Convert Pepper Simple message to PPAPI C++ var
+ pp::Var var(ps_event->as_var);
+ if (var.is_dictionary()) {
+ pp::VarDictionary dictionary(var);
+ std::string message = dictionary.Get("message").AsString();
+ if (message == "draw_points")
+ draw_points_ = dictionary.Get("value").AsBool();
+ else if (message == "draw_interiors")
+ draw_interiors_ = dictionary.Get("value").AsBool();
+ else if (message == "set_points") {
+ int num_points = dictionary.Get("value").AsInt();
+ point_count_ = std::min(kMaxPointCount, std::max(0, num_points));
+ } else if (message == "set_threads") {
+ int thread_count = dictionary.Get("value").AsInt();
+ delete workers_;
+ workers_ = new ThreadPool(thread_count);
+ }
+ }
+ }
+}
+
+// PostUpdateMessage() helper function for sendimg small messages to JS.
+void Voronoi::PostUpdateMessage(const char* message_name, double value) {
+ pp::VarDictionary message;
+ message.Set("message", message_name);
+ message.Set("value", value);
+ PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), message.pp_var());
+}
+
+void Voronoi::Update() {
+ PSContext2DGetBuffer(ps_context_);
+ if (NULL == ps_context_->data)
+ return;
+
+ UpdateSim();
+ Render();
+
+ PSContext2DSwapBuffer(ps_context_);
+}
+
+// Starting point for the module. We do not use main since it would
+// collide with main in libppapi_cpp.
+int example_main(int argc, char* argv[]) {
+ Voronoi voronoi;
+ while (true) {
+ PSEvent* ps_event;
+ // Consume all available events.
+ while ((ps_event = PSEventTryAcquire()) != NULL) {
+ voronoi.HandleEvent(ps_event);
+ PSEventRelease(ps_event);
+ }
+ // Do simulation, render and present.
+ voronoi.Update();
+ }
+
+ return 0;
+}
+
+// Register the function to call once the Instance Object is initialized.
+// see: pappi_simple/ps_main.h
+PPAPI_SIMPLE_REGISTER_MAIN(example_main);
diff --git a/native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/example.js b/native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/example.js
new file mode 100644
index 0000000..ff83571
--- /dev/null
+++ b/native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/example.js
@@ -0,0 +1,233 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+var naclModule = null;
+
+/**
+ * A helper function to abbreviate getElementById.
+ *
+ * @param {string} elementId The id to get.
+ * @return {Element}
+ */
+function $(elementId) {
+ return document.getElementById(elementId);
+}
+
+/**
+ * MIME type for PNaCl
+ *
+ * @return {string} MIME type
+ */
+function PNaClmimeType() {
+ return 'application/x-pnacl';
+}
+
+/**
+ * Check if the browser supports PNaCl.
+ *
+ * @return {bool}
+ */
+function browserSupportsPNaCl() {
+ var mimetype = PNaClmimeType();
+ return navigator.mimeTypes[mimetype] !== undefined;
+}
+
+/**
+ * Get the URL for Google Cloud Storage.
+ *
+ * @param {string} name The relative path to the file.
+ * @return {string}
+ */
+function getDataURL(name) {
+ var revision = 231029;
+ var baseUrl = 'http://commondatastorage.googleapis.com/gonacl/demos/publish/';
+ return baseUrl + revision + '/voronoi/' + name;
+}
+
+/**
+ * Create the Native Client <embed> element as a child of the DOM element
+ * named "listener".
+ *
+ * @param {string} name The name of the example.
+ * @param {number} width The width to create the plugin.
+ * @param {number} height The height to create the plugin.
+ * @param {Object} attrs Dictionary of attributes to set on the module.
+ */
+function createNaClModule(name, width, height, attrs) {
+ var moduleEl = document.createElement('embed');
+ moduleEl.setAttribute('name', 'nacl_module');
+ moduleEl.setAttribute('id', 'nacl_module');
+ moduleEl.setAttribute('width', width);
+ moduleEl.setAttribute('height', height);
+ moduleEl.setAttribute('path', '');
+ moduleEl.setAttribute('src', getDataURL(name + '.nmf'));
+ moduleEl.setAttribute('type', PNaClmimeType());
+
+ // Add any optional arguments
+ if (attrs) {
+ for (var key in attrs) {
+ moduleEl.setAttribute(key, attrs[key]);
+ }
+ }
+
+ // 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.
+ var listenerDiv = $('listener');
+ listenerDiv.appendChild(moduleEl);
+}
+
+/**
+ * Add the default event listeners to the element with id "listener".
+ */
+function attachDefaultListeners() {
+ var listenerDiv = $('listener');
+ listenerDiv.addEventListener('load', moduleDidLoad, true);
+ listenerDiv.addEventListener('error', moduleLoadError, true);
+ listenerDiv.addEventListener('progress', moduleLoadProgress, true);
+ listenerDiv.addEventListener('crash', handleCrash, true);
+ attachListeners();
+}
+
+/**
+ * Called when the Browser can not communicate with the Module
+ *
+ * This event listener is registered in attachDefaultListeners above.
+ *
+ * @param {Object} event
+ */
+function handleCrash(event) {
+ if (naclModule.exitStatus == -1) {
+ updateStatus('CRASHED');
+ } else {
+ updateStatus('EXITED [' + naclModule.exitStatus + ']');
+ }
+}
+
+/**
+ * Called when the NaCl module is loaded.
+ *
+ * This event listener is registered in attachDefaultListeners above.
+ */
+function moduleDidLoad() {
+ var bar = $('progress');
+ bar.value = 100;
+ bar.max = 100;
+ naclModule = $('nacl_module');
+ hideStatus();
+ setThreadCount();
+}
+
+/**
+ * Hide the status field and progress bar.
+ */
+function hideStatus() {
+ $('statusField').style.display = 'none';
+ $('progress').style.display = 'none';
+}
+
+/**
+ * Called when the plugin fails to load.
+ *
+ * @param {Object} event
+ */
+function moduleLoadError(event) {
+ updateStatus('Load failed.');
+}
+
+/**
+ * Called when the plugin reports progress events.
+ *
+ * @param {Object} event
+ */
+function moduleLoadProgress(event) {
+ $('progress').style.display = 'block';
+
+ var loadPercent = 0.0;
+ var bar = $('progress');
+ bar.max = 100;
+ if (event.lengthComputable && event.total > 0) {
+ loadPercent = event.loaded / event.total * 100.0;
+ } else {
+ // The total length is not yet known.
+ loadPercent = -1.0;
+ }
+ bar.value = loadPercent;
+}
+
+/**
+ * If the element with id 'statusField' exists, then set its HTML to the status
+ * message as well.
+ *
+ * @param {string} opt_message The message to set.
+ */
+function updateStatus(opt_message) {
+ var statusField = $('statusField');
+ if (statusField) {
+ statusField.style.display = 'block';
+ statusField.textContent = opt_message;
+ }
+}
+
+/**
+ * Send the current value of the element threadCount to the NaCl module.
+ *
+ * @param {number} threads The number of threads to use to render.
+ */
+function setThreadCount(threads) {
+ var value = parseInt($('threadCount').value);
+ naclModule.postMessage({'message': 'set_threads',
+ 'value': value});
+}
+
+/**
+ * Add event listeners after the NaCl module has loaded. These listeners will
+ * forward messages to the NaCl module via postMessage()
+ */
+function attachListeners() {
+ $('threadCount').addEventListener('change', setThreadCount);
+ $('drawPoints').addEventListener('click',
+ function() {
+ var checked = $('drawPoints').checked;
+ naclModule.postMessage({'message' : 'draw_points',
+ 'value' : checked});
+ });
+ $('drawInteriors').addEventListener('click',
+ function() {
+ var checked = $('drawInteriors').checked;
+ naclModule.postMessage({'message' : 'draw_interiors',
+ 'value' : checked});
+ });
+ $('pointRange').addEventListener('change',
+ function() {
+ var value = parseFloat($('pointRange').value);
+ naclModule.postMessage({'message' : 'set_points',
+ 'value' : value});
+ $('pointCount').textContent = value + ' points';
+ });
+}
+
+/**
+ * Listen for the DOM content to be loaded. This event is fired when parsing of
+ * the page's document has finished.
+ */
+document.addEventListener('DOMContentLoaded', function() {
+ updateStatus('Loading...');
+ if (!browserSupportsPNaCl()) {
+ updateStatus('Browser does not support PNaCl or PNaCl is disabled');
+ } else if (naclModule == null) {
+ createNaClModule('voronoi', 512, 512);
+ attachDefaultListeners();
+ } 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('Waiting.');
+ }
+});
diff --git a/native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/index.html b/native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/index.html
new file mode 100644
index 0000000..f5167b0
--- /dev/null
+++ b/native_client_sdk/src/gonacl_appengine/static/pnacl-demo-voronoi/index.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<!--
+Copyright (c) 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<head>
+ <meta charset="UTF-8">
+ <title>Voronoi</title>
+ <script type="text/javascript" src="example.js"></script>
+ <link href="/static/common.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+ <div class="absolute-fill">
+ <div class="flex-container">
+ <div class="main absolute-fill-parent">
+ <div class="absolute-fill">
+ <div class="flex-container flex-column flex-justify-center">
+ <div id="message">
+ <div id="statusField"></div>
+ <progress id="progress"></progress>
+ </div>
+ </div>
+ </div>
+ <div id="listener" class="absolute-fill"></div>
+ </div>
+ <div class="sidebar">
+ <h1>Voronoi</h1>
+ <p>
+ This demo renders the Voronoi diagram for a moving set of points using
+ a brute force technique.
+ </p>
+ <table id="config">
+ <tbody>
+ <tr>
+ <td class="name">Points:</td>
+ <td class="value">
+ <input type="range" id="pointRange"
+ min="1" max="1024" step="1" value="48">
+ <label id="pointCount">48 points</label>
+ </td>
+ </tr>
+ <tr>
+ <td class="name">Thread Count:</td>
+ <td class="value">
+ <select id="threadCount">
+ <option value="0">Main Thread only</option>
+ <option value="1">1 Thread</option>
+ <option value="2" selected="selected">2 Threads</option>
+ <option value="4">4 Threads</option>
+ <option value="6">6 Threads</option>
+ <option value="8">8 Threads</option>
+ <option value="12">12 Threads</option>
+ <option value="24">24 Threads</option>
+ <option value="32">32 Threads</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="name"><label for="drawPoints">Draw Points:</label></td>
+ <td class="value">
+ <input type="checkbox" id="drawPoints" checked="checked">
+ </td>
+ </tr>
+ <tr>
+ <td class="name">
+ <label for="drawInteriors">Draw Interiors:</label>
+ </td>
+ <td class="value">
+ <input type="checkbox" id="drawInteriors" checked="checked">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+</body>
+</html>