Index: native_client_sdk/src/examples/demo/voronoi/voronoi.cc |
=================================================================== |
--- native_client_sdk/src/examples/demo/voronoi/voronoi.cc (revision 0) |
+++ native_client_sdk/src/examples/demo/voronoi/voronoi.cc (revision 0) |
@@ -0,0 +1,582 @@ |
+// Copyright 2013 The Native Client SDK 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/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/module.h> |
+#include <ppapi/cpp/rect.h> |
+#include <ppapi/cpp/size.h> |
+#include <ppapi/cpp/var.h> |
+#include <pthread.h> |
+#include <stdio.h> |
+#include <stdlib.h> |
+#include <string.h> |
+#include <sys/time.h> |
+#include <unistd.h> |
+ |
+#include <string> |
+ |
+#include "threadpool.h" |
+ |
+// Global properties used to setup Voronoi demo. |
+namespace { |
+const int kMinRectSize = 4; |
+const int kStartRecurseSize = 32; |
+const float kHugeZ = 1.0e38f; |
+const float kPI = M_PI; |
+const float kTwoPI = kPI * 2.0f; |
+const int kFramesToBenchmark = 100; |
+const unsigned int kRandomStartSeed = 0xC0DE533D; |
+const int kMaxPointCount = 1024; |
+const int kStartPointCount = 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 double getseconds() { |
+ const double usec_to_sec = 0.000001; |
+ timeval tv; |
+ if (0 == gettimeofday(&tv, NULL)) |
+ return tv.tv_sec + tv.tv_usec * usec_to_sec; |
+ return 0.0; |
+} |
+ |
+inline uint32_t MakeRGBA(uint32_t r, uint32_t g, uint32_t b, 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 pp::Instance { |
+ public: |
+ explicit Voronoi(PP_Instance instance); |
+ virtual ~Voronoi(); |
+ |
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { |
+ return true; |
+ } |
+ |
+ virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); |
+ |
+ // Catch events. |
+ virtual bool HandleInputEvent(const pp::InputEvent& event); |
+ |
+ // Catch messages posted from Javascript. |
+ virtual void HandleMessage(const pp::Var& message); |
+ |
+ private: |
+ // Methods prefixed with 'w' are run on worker threads. |
+ 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(); |
+ void StartBenchmark(); |
+ void EndBenchmark(); |
+ |
+ // Runs a tick of the simulations, updating all buffers. Flushes the |
+ // contents of |image_data_| to the 2D graphics context. |
+ void Update(); |
+ |
+ // 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. |
+ void FlushPixelBuffer(); |
+ static void FlushCallback(void* data, int32_t result); |
+ |
+ |
+ pp::Graphics2D* graphics_2d_context_; |
+ pp::ImageData* image_data_; |
+ Vec2 positions_[kMaxPointCount]; |
+ Vec2 screen_positions_[kMaxPointCount]; |
+ Vec2 velocities_[kMaxPointCount]; |
+ uint32_t colors_[kMaxPointCount]; |
+ float ang_; |
+ int point_count_; |
+ int num_threads_; |
+ int num_regions_; |
+ bool draw_points_; |
+ bool draw_interiors_; |
+ ThreadPool *workers_; |
+ int width_; |
+ int height_; |
+ uint32_t *pixel_buffer_; |
+ int benchmark_frame_counter_; |
+ bool benchmarking_; |
+ double benchmark_start_time_; |
+ double benchmark_end_time_; |
+}; |
+ |
+ |
+ |
+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] = MakeRGBA(rand255(), rand255(), rand255(), 255); |
+ } |
+} |
+ |
+Voronoi::Voronoi(PP_Instance instance) : pp::Instance(instance), |
+ graphics_2d_context_(NULL), |
+ image_data_(NULL) { |
+ draw_points_ = true; |
+ draw_interiors_ = true; |
+ width_ = 0; |
+ height_ = 0; |
+ pixel_buffer_ = NULL; |
+ benchmark_frame_counter_ = 0; |
+ benchmarking_ = false; |
+ |
+ point_count_ = kStartPointCount; |
+ Reset(); |
+ |
+ // By default, do single threaded rendering. |
+ num_regions_ = 256; |
+ num_threads_ = 1; |
+ workers_ = new ThreadPool(num_threads_); |
+ |
+ // Request PPAPI input events for mouse & keyboard. |
+ RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); |
+ RequestInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); |
+} |
+ |
+Voronoi::~Voronoi() { |
+ delete workers_; |
+ DestroyContext(); |
+} |
+ |
+// 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 realative, 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 n = 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; |
+ n = i; |
+ } |
+ } |
+ // Return the closest cell to point x,y. |
+ return n; |
+} |
+ |
+// 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_); |
+ *w = width_ / parts; |
+ *h = 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. Assumes |
+// span width is divisible by 4. |
+// 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 = MakeRGBA(128, 128, 128, 255); |
+ color = gray; |
+ } |
+ for (int i = 0; i < width; i += 4) { |
+ *pixels++ = color; |
+ *pixels++ = color; |
+ *pixels++ = color; |
+ *pixels++ = color; |
+ } |
+} |
+ |
+// Quickly fill a rectangle with a solid color. Assumes |
+// the width w parameter is evenly divisible by 4. |
+// 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 pitch = width_; |
+ uint32_t *pixels = pixel_buffer_ + y * pitch + x; |
+ for (int j = 0; j < h; j++) { |
+ wFillSpan(pixels, color, w); |
+ pixels += pitch; |
+ } |
+} |
+ |
+// 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 pitch = width_; |
+ uint32_t *pixels = pixel_buffer_ + y * pitch + x; |
+ 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 += pitch; |
+ } |
+} |
+ |
+// 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. |
+// Note: at the moment, these will always be squares, not 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, half_w, half_h); |
+ wSubdivide(x, y + half_h, half_w, half_h); |
+ wSubdivide(x + half_w, y + half_h, half_w, half_h); |
+ } |
+ } |
+} |
+ |
+// This function cuts up the rectangle into power of 2 sized squares. It |
+// assumes the input rectangle w & h are evenly divisible by |
+// kStartRecurseSize. |
+// 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) { |
+ wSubdivide(ix, iy, kStartRecurseSize, kStartRecurseSize); |
+ } |
+ } |
+} |
+ |
+// 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 * width_; |
+ screen_positions_[j].y = positions_[j].y * 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); |
+ int pitch = width_; |
+ // clip it against window |
+ if (ix < 1) return; |
+ if (ix >= (width_ - 1)) return; |
+ if (iy < 1) return; |
+ if (iy >= (height_ - 1)) return; |
+ uint32_t *pixel = pixel_buffer_ + iy * pitch + ix; |
+ // render dot as a small diamond |
+ *pixel = color1; |
+ *(pixel - 1) = color2; |
+ *(pixel + 1) = color2; |
+ *(pixel - pitch) = color2; |
+ *(pixel + pitch) = color2; |
+} |
+ |
+// Superimposes dots on the positions. |
+void Voronoi::SuperimposePositions() { |
+ const uint32_t white = MakeRGBA(255, 255, 255, 255); |
+ const uint32_t gray = MakeRGBA(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. |
+// Note: This Dispatch() is from the main PPAPI thread, so care must be taken |
+// not to attempt PPAPI calls from the worker threads, since Dispatch() will |
+// block here until all work is complete. The worker threads are compute only |
+// and do not make any PPAPI calls. |
+void Voronoi::Render() { |
+ workers_->Dispatch(num_regions_, wRenderRegionEntry, this); |
+ if (draw_points_) |
+ SuperimposePositions(); |
+} |
+ |
+void Voronoi::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()); |
+ Update(); |
+} |
+ |
+void Voronoi::StartBenchmark() { |
+ Reset(); |
+ printf("Benchmark started...\n"); |
+ benchmark_frame_counter_ = kFramesToBenchmark; |
+ benchmarking_ = true; |
+ benchmark_start_time_ = getseconds(); |
+} |
+ |
+void Voronoi::EndBenchmark() { |
+ benchmark_end_time_ = getseconds(); |
+ printf("Benchmark ended... time: %2.5f\n", |
+ benchmark_end_time_ - benchmark_start_time_); |
+ benchmarking_ = false; |
+ benchmark_frame_counter_ = 0; |
+ pp::Var result(benchmark_end_time_ - benchmark_start_time_); |
+ PostMessage(result); |
+} |
+ |
+// Handle input events from the user. |
+bool Voronoi::HandleInputEvent(const pp::InputEvent& event) { |
+ switch (event.GetType()) { |
+ case PP_INPUTEVENT_TYPE_KEYDOWN: { |
+ pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); |
+ uint32_t key_code = key.GetKeyCode(); |
+ if (key_code == 84) // 't' key |
+ if (!benchmarking_) |
+ StartBenchmark(); |
+ break; |
+ } |
+ default: |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+// Handle messages sent from Javascript. |
+void Voronoi::HandleMessage(const pp::Var& message) { |
+ if (message.is_string()) { |
+ std::string message_string = message.AsString(); |
+ if (message_string == "run benchmark" && !benchmarking_) |
+ StartBenchmark(); |
+ else if (message_string == "with points") |
+ draw_points_ = true; |
+ else if (message_string == "without points") |
+ draw_points_ = false; |
+ else if (message_string == "with interiors") |
+ draw_interiors_ = true; |
+ else if (message_string == "without interiors") |
+ draw_interiors_ = false; |
+ else if (strstr(message_string.c_str(), "points:")) { |
+ int num_points = atoi(strstr(message_string.c_str(), " ")); |
+ point_count_ = num_points < kMaxPointCount ? num_points : kMaxPointCount; |
+ } else if (strstr(message_string.c_str(), "threads:")) { |
+ int thread_count = atoi(strstr(message_string.c_str(), " ")); |
+ delete workers_; |
+ workers_ = new ThreadPool(thread_count); |
+ } |
+ } |
+} |
+ |
+void Voronoi::FlushCallback(void* thiz, int32_t result) { |
+ static_cast<Voronoi*>(thiz)->Update(); |
+} |
+ |
+// Update the 2d region and flush to make it visible on the page. |
+void Voronoi::FlushPixelBuffer() { |
+ graphics_2d_context_->PaintImageData(*image_data_, pp::Point(0, 0)); |
+ graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this)); |
+} |
+ |
+void Voronoi::Update() { |
+ // Don't call FlushPixelBuffer() when benchmarking - vsync is enabled by |
+ // default, and will throttle the benchmark results. |
+ do { |
+ UpdateSim(); |
+ Render(); |
+ if (!benchmarking_) break; |
+ --benchmark_frame_counter_; |
+ } while (benchmark_frame_counter_ > 0); |
+ if (benchmarking_) |
+ EndBenchmark(); |
+ FlushPixelBuffer(); |
+} |
+ |
+void Voronoi::CreateContext(const pp::Size& size) { |
+ const size_t bytes = size.width() * size.height(); |
+ graphics_2d_context_ = new pp::Graphics2D(this, size, false); |
+ if (!BindGraphics(*graphics_2d_context_)) |
+ printf("Couldn't bind the device context\n"); |
+ image_data_ = new pp::ImageData(this, |
+ PP_IMAGEDATAFORMAT_BGRA_PREMUL, |
+ size, |
+ false); |
+ width_ = image_data_->size().width(); |
+ height_ = image_data_->size().height(); |
+ pixel_buffer_ = reinterpret_cast<uint32_t*>(image_data_->data()); |
+} |
+ |
+void Voronoi::DestroyContext() { |
+ delete graphics_2d_context_; |
+ delete image_data_; |
+ graphics_2d_context_ = NULL; |
+ image_data_ = NULL; |
+ width_ = 0; |
+ height_ = 0; |
+ pixel_buffer_ = NULL; |
+} |
+ |
+// 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 VoronoiModule : public pp::Module { |
+ public: |
+ VoronoiModule() : pp::Module() {} |
+ virtual ~VoronoiModule() {} |
+ |
+ // Create and return a Voronoi instance. |
+ virtual pp::Instance* CreateInstance(PP_Instance instance) { |
+ return new Voronoi(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 VoronoiModule(); |
+} |
+} // namespace pp |
+ |
Property changes on: native_client_sdk/src/examples/demo/voronoi/voronoi.cc |
___________________________________________________________________ |
Added: svn:executable |
+ * |