diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-25 22:19:36 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-25 22:19:36 +0000 |
commit | bc6cc6f9b04138e9fd09f00a9d00633734189af9 (patch) | |
tree | f8c5db06ac240e3d8573cf6deb6e618f7fca632f /native_client_sdk/src/gonacl_appengine | |
parent | 4acc1772033deb97cad3e0edef84cf063fac5cc1 (diff) | |
download | chromium_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
Diffstat (limited to 'native_client_sdk/src/gonacl_appengine')
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> |