Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(212)

Side by Side Diff: native_client_sdk/src/examples/demo/voronoi/voronoi.cc

Issue 15732012: Revive voronoi multi-threaded demo. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:executable
+ *
OLDNEW
(Empty)
1 // Copyright 2013 The Native Client SDK Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can
3 // be found in the LICENSE file.
4
5 #include <assert.h>
6 #include <math.h>
7 #include <ppapi/cpp/completion_callback.h>
8 #include <ppapi/cpp/graphics_2d.h>
9 #include <ppapi/cpp/image_data.h>
10 #include <ppapi/cpp/input_event.h>
11 #include <ppapi/cpp/instance.h>
12 #include <ppapi/cpp/module.h>
13 #include <ppapi/cpp/rect.h>
14 #include <ppapi/cpp/size.h>
15 #include <ppapi/cpp/var.h>
16 #include <pthread.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/time.h>
21 #include <unistd.h>
22
23 #include <string>
24
25 #include "threadpool.h"
26
27 // Global properties used to setup Voronoi demo.
28 namespace {
29 const int kMinRectSize = 4;
30 const int kStartRecurseSize = 32;
binji 2013/05/24 21:47:55 must be power of 2
nfullagar1 2013/05/28 23:11:50 comment added
31 const float kHugeZ = 1.0e38f;
32 const float kPI = M_PI;
33 const float kTwoPI = kPI * 2.0f;
34 const int kFramesToBenchmark = 100;
35 const unsigned int kRandomStartSeed = 0xC0DE533D;
36 const int kMaxPointCount = 1024;
37 const int kStartPointCount = 256;
38
39 unsigned int g_rand_state = kRandomStartSeed;
40
41 // random number helper
42 inline unsigned char rand255() {
43 return static_cast<unsigned char>(rand_r(&g_rand_state) & 255);
44 }
45
46 // random number helper
47 inline float frand() {
48 return (static_cast<float>(rand_r(&g_rand_state)) /
49 static_cast<float>(RAND_MAX));
50 }
51
52 // reset random seed
53 inline void rand_reset(unsigned int seed) {
54 g_rand_state = seed;
55 }
56
57 inline double getseconds() {
58 const double usec_to_sec = 0.000001;
59 timeval tv;
60 if (0 == gettimeofday(&tv, NULL))
61 return tv.tv_sec + tv.tv_usec * usec_to_sec;
62 return 0.0;
63 }
64
65 inline uint32_t MakeRGBA(uint32_t r, uint32_t g, uint32_t b, uint32_t a) {
66 return (((a) << 24) | ((r) << 16) | ((g) << 8) | (b));
67 }
68 } // namespace
69
70 // Vec2, simple 2D vector
71 struct Vec2 {
72 float x, y;
73 Vec2() { ; }
binji 2013/05/24 21:47:55 init to 0?
noelallen1 2013/05/25 00:21:11 We typically use {} not {;}
nfullagar1 2013/05/28 23:11:50 This struct is closer to an int or a float (in my
nfullagar1 2013/05/28 23:11:50 Done.
74 Vec2(float px, float py) {
75 x = px;
76 y = py;
77 }
78 void Set(float px, float py) {
79 x = px;
80 y = py;
81 }
82 };
83
84 // The main object that runs Voronoi simulation.
85 class Voronoi : public pp::Instance {
86 public:
87 explicit Voronoi(PP_Instance instance);
88 virtual ~Voronoi();
89
90 virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
91 return true;
92 }
93
94 virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip);
95
96 // Catch events.
97 virtual bool HandleInputEvent(const pp::InputEvent& event);
98
99 // Catch messages posted from Javascript.
100 virtual void HandleMessage(const pp::Var& message);
101
102 private:
103 // Methods prefixed with 'w' are run on worker threads.
104 int wCell(float x, float y);
105 inline void wFillSpan(uint32_t *pixels, uint32_t color, int width);
106 void wRenderTile(int x, int y, int w, int h);
107 void wProcessTile(int x, int y, int w, int h);
108 void wSubdivide(int x, int y, int w, int h);
109 void wMakeRect(int region, int *x, int *y, int *w, int *h);
110 bool wTestRect(int *m, int x, int y, int w, int h);
111 void wFillRect(int x, int y, int w, int h, uint32_t color);
112 void wRenderRect(int x0, int y0, int x1, int y1);
113 void wRenderRegion(int region);
114 static void wRenderRegionEntry(int region, void *thiz);
115
116 // These methods are only called by the main thread.
117 void Reset();
118 void UpdateSim();
119 void RenderDot(float x, float y, uint32_t color1, uint32_t color2);
120 void SuperimposePositions();
121 void Render();
122 void Draw();
123 void StartBenchmark();
124 void EndBenchmark();
125
126 // Runs a tick of the simulations, updating all buffers. Flushes the
127 // contents of |image_data_| to the 2D graphics context.
128 void Update();
129
130 // Create and initialize the 2D context used for drawing.
131 void CreateContext(const pp::Size& size);
132 // Destroy the 2D drawing context.
133 void DestroyContext();
134 // Push the pixels to the browser, then attempt to flush the 2D context.
135 void FlushPixelBuffer();
136 static void FlushCallback(void* data, int32_t result);
137
138
139 pp::Graphics2D* graphics_2d_context_;
140 pp::ImageData* image_data_;
141 Vec2 positions_[kMaxPointCount];
142 Vec2 screen_positions_[kMaxPointCount];
143 Vec2 velocities_[kMaxPointCount];
144 uint32_t colors_[kMaxPointCount];
145 float ang_;
146 int point_count_;
147 int num_threads_;
148 int num_regions_;
149 bool draw_points_;
150 bool draw_interiors_;
151 ThreadPool *workers_;
152 int width_;
153 int height_;
154 uint32_t stride_in_pixels_;
155 uint32_t *pixel_buffer_;
156 int benchmark_frame_counter_;
157 bool benchmarking_;
158 double benchmark_start_time_;
159 double benchmark_end_time_;
160 };
161
162
163
164 void Voronoi::Reset() {
165 rand_reset(kRandomStartSeed);
166 ang_ = 0.0f;
167 for (int i = 0; i < kMaxPointCount; i++) {
168 // random initial start position
169 const float x = frand();
170 const float y = frand();
171 positions_[i].Set(x, y);
172 // random directional velocity ( -1..1, -1..1 )
173 const float speed = 0.0005f;
174 const float u = (frand() * 2.0f - 1.0f) * speed;
175 const float v = (frand() * 2.0f - 1.0f) * speed;
176 velocities_[i].Set(u, v);
177 // 'unique' color (well... unique enough for our purposes)
178 colors_[i] = MakeRGBA(rand255(), rand255(), rand255(), 255);
179 }
180 }
181
182 Voronoi::Voronoi(PP_Instance instance) : pp::Instance(instance),
183 graphics_2d_context_(NULL),
184 image_data_(NULL) {
185 draw_points_ = true;
186 draw_interiors_ = true;
187 width_ = 0;
188 height_ = 0;
189 stride_in_pixels_ = 0;
190 pixel_buffer_ = NULL;
191 benchmark_frame_counter_ = 0;
192 benchmarking_ = false;
193
194 point_count_ = kStartPointCount;
195 Reset();
196
197 // By default, do single threaded rendering.
198 num_regions_ = 256;
199 num_threads_ = 1;
200 workers_ = new ThreadPool(num_threads_);
201
202 // Request PPAPI input events for mouse & keyboard.
203 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
noelallen1 2013/05/25 00:21:11 You aren't looking at mouse events.
nfullagar1 2013/05/28 23:11:50 Not now. The thought being if a user makes local
204 RequestInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
205 }
206
207 Voronoi::~Voronoi() {
208 delete workers_;
209 DestroyContext();
210 }
211
212 // This is the core of the Voronoi calculation. At a given point on the
213 // screen, iterate through all voronoi positions and render them as 3D cones.
214 // We're looking for the voronoi cell that generates the closest z value.
215 // (not really cones - since it is all realative, we avoid doing the
binji 2013/05/24 21:47:55 sp: relative
nfullagar1 2013/05/28 23:11:50 Done.
216 // expensive sqrt and test against z*z instead)
217 // If multithreading, this function is only called by the worker threads.
218 int Voronoi::wCell(float x, float y) {
219 int n = 0;
binji 2013/05/24 21:47:55 maybe a better var name here... closest_index?
nfullagar1 2013/05/28 23:11:50 s/n/closest_cell, removed comment on return statem
220 float zz = kHugeZ;
binji 2013/05/24 21:47:55 maybe minz?
nfullagar1 2013/05/28 23:11:50 Is comment above sufficient?
221 Vec2 *pos = screen_positions_;
222 for (int i = 0; i < point_count_; ++i) {
223 // measured 5.18 cycles per iteration on a core2
224 float dx = x - pos[i].x;
225 float dy = y - pos[i].y;
226 float dd = (dx * dx + dy * dy);
227 if (dd < zz) {
228 zz = dd;
229 n = i;
230 }
231 }
232 // Return the closest cell to point x,y.
233 return n;
234 }
235
236 // Given a region r, derive a non-overlapping rectangle for a thread to
237 // work on.
238 // If multithreading, this function is only called by the worker threads.
239 void Voronoi::wMakeRect(int r, int *x, int *y, int *w, int *h) {
240 const int parts = 16;
241 assert(parts * parts == num_regions_);
binji 2013/05/24 21:47:55 num_regions should be const too, I think.
nfullagar1 2013/05/28 23:11:50 Done.
binji 2013/05/28 23:37:50 Ah, I just meant it seemed strange to have parts d
242 *w = width_ / parts;
243 *h = height_ / parts;
244 *x = *w * (r % parts);
245 *y = *h * ((r / parts) % parts);
246 }
247
248 // Test 4 corners of a rectangle to see if they all belong to the same
249 // voronoi cell. Each test is expensive so bail asap. Returns true
250 // if all 4 corners match.
251 // If multithreading, this function is only called by the worker threads.
252 bool Voronoi::wTestRect(int *m, int x, int y, int w, int h) {
253 // each test is expensive, so exit ASAP
254 const int m0 = wCell(x, y);
255 const int m1 = wCell(x + w - 1, y);
256 if (m0 != m1) return false;
257 const int m2 = wCell(x, y + h - 1);
258 if (m0 != m2) return false;
259 const int m3 = wCell(x + w - 1, y + h - 1);
260 if (m0 != m3) return false;
261 // all 4 corners belong to the same cell
262 *m = m0;
263 return true;
264 }
265
266 // Quickly fill a span of pixels with a solid color. Assumes
267 // span width is divisible by 4.
268 // If multithreading, this function is only called by the worker threads.
269 inline void Voronoi::wFillSpan(uint32_t *pixels, uint32_t color, int width) {
270 if (!draw_interiors_) {
271 const uint32_t gray = MakeRGBA(128, 128, 128, 255);
272 color = gray;
273 }
274 for (int i = 0; i < width; i += 4) {
275 *pixels++ = color;
276 *pixels++ = color;
277 *pixels++ = color;
278 *pixels++ = color;
279 }
280 }
281
282 // Quickly fill a rectangle with a solid color. Assumes
283 // the width w parameter is evenly divisible by 4.
284 // If multithreading, this function is only called by the worker threads.
285 void Voronoi::wFillRect(int x, int y, int w, int h, uint32_t color) {
286 const uint32_t pitch = width_;
287 uint32_t *pixels = pixel_buffer_ + y * pitch + x;
288 for (int j = 0; j < h; j++) {
289 wFillSpan(pixels, color, w);
290 pixels += pitch;
291 }
292 }
293
294 // When recursive subdivision reaches a certain minimum without finding a
295 // rectangle that has four matching corners belonging to the same voronoi
296 // cell, this function will break the retangular 'tile' into smaller scanlines
297 // and look for opportunities to quick fill at the scanline level. If the
298 // scanline can't be quick filled, it will slow down even further and compute
299 // voronoi membership per pixel.
300 void Voronoi::wRenderTile(int x, int y, int w, int h) {
301 // rip through a tile
302 uint32_t *pixels = pixel_buffer_ + y * stride_in_pixels_ + x;
303 for (int j = 0; j < h; j++) {
304 // get start and end cell values
305 int ms = wCell(x + 0, y + j);
306 int me = wCell(x + w - 1, y + j);
307 // if the end points are the same, quick fill the span
308 if (ms == me) {
309 wFillSpan(pixels, colors_[ms], w);
310 } else {
311 // else compute each pixel in the span... this is the slow part!
312 uint32_t *p = pixels;
313 *p++ = colors_[ms];
314 for (int i = 1; i < (w - 1); i++) {
315 int m = wCell(x + i, y + j);
316 *p++ = colors_[m];
317 }
318 *p++ = colors_[me];
319 }
320 pixels += stride_in_pixels_;
321 }
322 }
323
324 // Take a rectangular region and do one of -
325 // If all four corners below to the same voronoi cell, stop recursion and
326 // quick fill the rectangle.
327 // If the minimum rectangle size has been reached, break out of recursion
328 // and process the rectangle. This small rectangle is called a tile.
329 // Otherwise, keep recursively subdividing the rectangle into 4 equally
330 // sized smaller rectangles.
331 // Note: at the moment, these will always be squares, not rectangles.
332 // If multithreading, this function is only called by the worker threads.
333 void Voronoi::wSubdivide(int x, int y, int w, int h) {
334 int m;
335 // if all 4 corners are equal, quick fill interior
336 if (wTestRect(&m, x, y, w, h)) {
337 wFillRect(x, y, w, h, colors_[m]);
338 } else {
339 // did we reach the minimum rectangle size?
340 if ((w <= kMinRectSize) || (h <= kMinRectSize)) {
341 wRenderTile(x, y, w, h);
342 } else {
343 // else recurse into smaller rectangles
344 const int half_w = w / 2;
345 const int half_h = h / 2;
346 wSubdivide(x, y, half_w, half_h);
347 wSubdivide(x + half_w, y, half_w, half_h);
348 wSubdivide(x, y + half_h, half_w, half_h);
349 wSubdivide(x + half_w, y + half_h, half_w, half_h);
350 }
351 }
352 }
353
354 // This function cuts up the rectangle into power of 2 sized squares. It
355 // assumes the input rectangle w & h are evenly divisible by
356 // kStartRecurseSize.
357 // If multithreading, this function is only called by the worker threads.
358 void Voronoi::wRenderRect(int x, int y, int w, int h) {
359 for (int iy = y; iy < (y + h); iy += kStartRecurseSize) {
360 for (int ix = x; ix < (x + w); ix += kStartRecurseSize) {
361 wSubdivide(ix, iy, kStartRecurseSize, kStartRecurseSize);
362 }
363 }
364 }
365
366 // If multithreading, this function is only called by the worker threads.
367 void Voronoi::wRenderRegion(int region) {
368 // convert region # into x0, y0, x1, y1 rectangle
369 int x, y, w, h;
370 wMakeRect(region, &x, &y, &w, &h);
371 // render this rectangle
372 wRenderRect(x, y, w, h);
binji 2013/05/24 21:47:55 assert that w, h are power of two?
nfullagar1 2013/05/28 23:11:50 done (but assert is down in CreateContext)
373 }
374
375 // Entry point for worker thread. Can't pass a member function around, so we
376 // have to do this little round-about.
377 void Voronoi::wRenderRegionEntry(int region, void *thiz) {
binji 2013/05/24 21:47:55 I've been calling this a "thunk"
nfullagar1 2013/05/28 23:11:50 hmm... if you don't feel strongly about this, it f
binji 2013/05/28 23:37:50 Nope, Entry is fine.
378 static_cast<Voronoi*>(thiz)->wRenderRegion(region);
379 }
380
381 // Function Voronoi::UpdateSim()
382 // Run a simple sim to move the voronoi positions. This update loop
383 // is run once per frame. Called from the main thread only, and only
384 // when the worker threads are idle.
385 void Voronoi::UpdateSim() {
386 ang_ += 0.002f;
387 if (ang_ > kTwoPI) {
388 ang_ = ang_ - kTwoPI;
389 }
390 float z = cosf(ang_) * 3.0f;
391 // push the points around on the screen for animation
392 for (int j = 0; j < kMaxPointCount; j++) {
393 positions_[j].x += (velocities_[j].x) * z;
394 positions_[j].y += (velocities_[j].y) * z;
395 screen_positions_[j].x = positions_[j].x * width_;
396 screen_positions_[j].y = positions_[j].y * height_;
397 }
398 }
399
400 // Renders a small diamond shaped dot at x, y clipped against the window
401 void Voronoi::RenderDot(float x, float y, uint32_t color1, uint32_t color2) {
402 const int ix = static_cast<int>(x);
403 const int iy = static_cast<int>(y);
404 // clip it against window
405 if (ix < 1) return;
406 if (ix >= (width_ - 1)) return;
407 if (iy < 1) return;
408 if (iy >= (height_ - 1)) return;
409 uint32_t *pixel = pixel_buffer_ + iy * stride_in_pixels_ + ix;
410 // render dot as a small diamond
411 *pixel = color1;
412 *(pixel - 1) = color2;
413 *(pixel + 1) = color2;
414 *(pixel - stride_in_pixels_) = color2;
415 *(pixel + stride_in_pixels_) = color2;
416 }
417
418 // Superimposes dots on the positions.
419 void Voronoi::SuperimposePositions() {
420 const uint32_t white = MakeRGBA(255, 255, 255, 255);
421 const uint32_t gray = MakeRGBA(192, 192, 192, 255);
422 for (int i = 0; i < point_count_; i++) {
423 RenderDot(
424 screen_positions_[i].x, screen_positions_[i].y, white, gray);
425 }
426 }
427
428 // Renders the Voronoi diagram, dispatching the work to multiple threads.
429 // Note: This Dispatch() is from the main PPAPI thread, so care must be taken
430 // not to attempt PPAPI calls from the worker threads, since Dispatch() will
431 // block here until all work is complete. The worker threads are compute only
432 // and do not make any PPAPI calls.
433 void Voronoi::Render() {
434 workers_->Dispatch(num_regions_, wRenderRegionEntry, this);
435 if (draw_points_)
436 SuperimposePositions();
437 }
438
439 void Voronoi::DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
440 if (position.size().width() == width_ &&
441 position.size().height() == height_)
442 return; // Size didn't change, no need to update anything.
443
444 // Create a new device context with the new size.
445 DestroyContext();
446 CreateContext(position.size());
447 Update();
448 }
449
450 void Voronoi::StartBenchmark() {
451 Reset();
452 printf("Benchmark started...\n");
453 benchmark_frame_counter_ = kFramesToBenchmark;
454 benchmarking_ = true;
455 benchmark_start_time_ = getseconds();
456 }
457
458 void Voronoi::EndBenchmark() {
459 benchmark_end_time_ = getseconds();
460 printf("Benchmark ended... time: %2.5f\n",
461 benchmark_end_time_ - benchmark_start_time_);
462 benchmarking_ = false;
463 benchmark_frame_counter_ = 0;
464 pp::Var result(benchmark_end_time_ - benchmark_start_time_);
465 PostMessage(result);
466 }
467
468 // Handle input events from the user.
469 bool Voronoi::HandleInputEvent(const pp::InputEvent& event) {
470 switch (event.GetType()) {
471 case PP_INPUTEVENT_TYPE_KEYDOWN: {
472 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
473 uint32_t key_code = key.GetKeyCode();
474 if (key_code == 84) // 't' key
475 if (!benchmarking_)
476 StartBenchmark();
477 break;
478 }
479 default:
480 return false;
481 }
482 return true;
483 }
484
485 // Handle messages sent from Javascript.
486 void Voronoi::HandleMessage(const pp::Var& message) {
487 if (message.is_string()) {
488 std::string message_string = message.AsString();
489 if (message_string == "run benchmark" && !benchmarking_)
490 StartBenchmark();
491 else if (message_string == "with points")
492 draw_points_ = true;
493 else if (message_string == "without points")
494 draw_points_ = false;
495 else if (message_string == "with interiors")
496 draw_interiors_ = true;
497 else if (message_string == "without interiors")
498 draw_interiors_ = false;
499 else if (strstr(message_string.c_str(), "points:")) {
500 int num_points = atoi(strstr(message_string.c_str(), " "));
501 point_count_ = num_points < kMaxPointCount ? num_points : kMaxPointCount;
binji 2013/05/24 21:47:55 check for negative?
nfullagar1 2013/05/28 23:11:50 Done.
502 } else if (strstr(message_string.c_str(), "threads:")) {
503 int thread_count = atoi(strstr(message_string.c_str(), " "));
504 delete workers_;
505 workers_ = new ThreadPool(thread_count);
506 }
507 }
508 }
509
510 void Voronoi::FlushCallback(void* thiz, int32_t result) {
511 static_cast<Voronoi*>(thiz)->Update();
512 }
513
514 // Update the 2d region and flush to make it visible on the page.
515 void Voronoi::FlushPixelBuffer() {
516 graphics_2d_context_->PaintImageData(*image_data_, pp::Point(0, 0));
517 graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this));
518 }
519
520 void Voronoi::Update() {
521 // Don't call FlushPixelBuffer() when benchmarking - vsync is enabled by
522 // default, and will throttle the benchmark results.
523 do {
524 UpdateSim();
525 Render();
526 if (!benchmarking_) break;
527 --benchmark_frame_counter_;
528 } while (benchmark_frame_counter_ > 0);
529 if (benchmarking_)
530 EndBenchmark();
531 FlushPixelBuffer();
532 }
533
534 void Voronoi::CreateContext(const pp::Size& size) {
535 const size_t bytes = size.width() * size.height();
binji 2013/05/24 21:47:55 unused
nfullagar1 2013/05/28 23:11:50 good catch
536 graphics_2d_context_ = new pp::Graphics2D(this, size, false);
537 if (!BindGraphics(*graphics_2d_context_))
538 printf("Couldn't bind the device context\n");
539 image_data_ = new pp::ImageData(this,
540 PP_IMAGEDATAFORMAT_BGRA_PREMUL,
541 size,
542 false);
543 width_ = image_data_->size().width();
544 height_ = image_data_->size().height();
545 stride_in_pixels_ = static_cast<uint32_t>(image_data_->stride() / 4);
546 pixel_buffer_ = static_cast<uint32_t*>(image_data_->data());
547 }
548
549 void Voronoi::DestroyContext() {
550 delete graphics_2d_context_;
551 delete image_data_;
552 graphics_2d_context_ = NULL;
553 image_data_ = NULL;
554 width_ = 0;
555 height_ = 0;
556 stride_in_pixels_ = 0;
557 pixel_buffer_ = NULL;
558 }
559
560 // The Module class. The browser calls the CreateInstance() method to create
binji 2013/05/24 21:47:55 kill comment
nfullagar1 2013/05/28 23:11:50 Done.
561 // an instance of you NaCl module on the web page. The browser creates a new
562 // instance for each <embed> tag with type="application/x-nacl".
563 class VoronoiModule : public pp::Module {
564 public:
565 VoronoiModule() : pp::Module() {}
566 virtual ~VoronoiModule() {}
567
568 // Create and return a Voronoi instance.
569 virtual pp::Instance* CreateInstance(PP_Instance instance) {
570 return new Voronoi(instance);
571 }
572 };
573
574 // Factory function called by the browser when the module is first loaded.
binji 2013/05/24 21:47:55 kill comment
nfullagar1 2013/05/28 23:11:50 Done.
575 // The browser keeps a singleton of this module. It calls the
576 // CreateInstance() method on the object you return to make instances. There
577 // is one instance per <embed> tag on the page. This is the main binding
578 // point for your NaCl module with the browser.
579 namespace pp {
580 Module* CreateModule() {
581 return new VoronoiModule();
582 }
583 } // namespace pp
584
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698