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

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

Powered by Google App Engine
This is Rietveld 408576698