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/c/ppb_input_event.h> 8 #include <ppapi/cpp/input_event.h> 9 #include <ppapi/cpp/var.h> 10 #include <ppapi/cpp/var_array.h> 11 #include <ppapi/cpp/var_array_buffer.h> 12 #include <ppapi/cpp/var_dictionary.h> 13 #include <pthread.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 #include <sys/time.h> 18 #include <unistd.h> 19 20 #include <algorithm> 21 #include <string> 22 23 #include "ppapi_simple/ps.h" 24 #include "ppapi_simple/ps_context_2d.h" 25 #include "ppapi_simple/ps_event.h" 26 #include "ppapi_simple/ps_interface.h" 27 #include "ppapi_simple/ps_main.h" 28 #include "sdk_util/macros.h" 29 #include "sdk_util/thread_pool.h" 30 31 using namespace sdk_util; // For sdk_util::ThreadPool 32 33 // Global properties used to setup Earth demo. 34 namespace { 35 const float kHugeZ = 1.0e38f; 36 const float kPI = M_PI; 37 const float kTwoPI = kPI * 2.0f; 38 const float kOneOverPI = 1.0f / kPI; 39 const float kOneOver2PI = 1.0f / kTwoPI; 40 const float kOneOver255 = 1.0f / 255.0f; 41 const int kArcCosineTableSize = 4096; 42 const int kFramesToBenchmark = 100; 43 const float kZoomMin = 1.0f; 44 const float kZoomMax = 50.0f; 45 const float kWheelSpeed = 2.0f; 46 const float kLightMin = 0.0f; 47 const float kLightMax = 2.0f; 48 const int kFrameTimeBufferSize = 512; 49 50 // Timer helper for benchmarking. Returns seconds elapsed since program start, 51 // as a double. 52 timeval start_tv; 53 int start_tv_retv = gettimeofday(&start_tv, NULL); 54 55 inline double getseconds() { 56 const double usec_to_sec = 0.000001; 57 timeval tv; 58 if ((0 == start_tv_retv) && (0 == gettimeofday(&tv, NULL))) 59 return (tv.tv_sec - start_tv.tv_sec) + tv.tv_usec * usec_to_sec; 60 return 0.0; 61 } 62 63 // RGBA helper functions, used for extracting color from RGBA source image. 64 inline float ExtractR(uint32_t c) { 65 return static_cast<float>(c & 0xFF) * kOneOver255; 66 } 67 68 inline float ExtractG(uint32_t c) { 69 return static_cast<float>((c & 0xFF00) >> 8) * kOneOver255; 70 } 71 72 inline float ExtractB(uint32_t c) { 73 return static_cast<float>((c & 0xFF0000) >> 16) * kOneOver255; 74 } 75 76 // BGRA helper function, for constructing a pixel for a BGRA buffer. 77 inline uint32_t MakeBGRA(uint32_t b, uint32_t g, uint32_t r, uint32_t a) { 78 return (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)); 79 } 80 81 // simple container for earth texture 82 struct Texture { 83 int width, height; 84 uint32_t* pixels; 85 Texture(int w, int h) : width(w), height(h) { 86 pixels = new uint32_t[w * h]; 87 memset(pixels, 0, sizeof(uint32_t) * w * h); 88 } 89 explicit Texture(int w, int h, uint32_t* p) : width(w), height(h) { 90 pixels = new uint32_t[w * h]; 91 memcpy(pixels, p, sizeof(uint32_t) * w * h); 92 } 93 ~Texture() { delete[] pixels; } 94 95 DISALLOW_COPY_AND_ASSIGN(Texture); 96 }; 97 98 99 100 struct ArcCosine { 101 // slightly larger table so we can interpolate beyond table size 102 float table[kArcCosineTableSize + 2]; 103 float TableLerp(float x); 104 ArcCosine(); 105 }; 106 107 ArcCosine::ArcCosine() { 108 // build a slightly larger table to allow for numeric imprecision 109 for (int i = 0; i < (kArcCosineTableSize + 2); ++i) { 110 float f = static_cast<float>(i) / kArcCosineTableSize; 111 f = f * 2.0f - 1.0f; 112 table[i] = acos(f); 113 } 114 } 115 116 // looks up acos(f) using a table and lerping between entries 117 // (it is expected that input f is between -1 and 1) 118 float ArcCosine::TableLerp(float f) { 119 float x = (f + 1.0f) * 0.5f; 120 x = x * kArcCosineTableSize; 121 int ix = static_cast<int>(x); 122 float fx = static_cast<float>(ix); 123 float dx = x - fx; 124 float af = table[ix]; 125 float af2 = table[ix + 1]; 126 return af + (af2 - af) * dx; 127 } 128 129 // Helper functions for quick but approximate sqrt. 130 union Convert { 131 float f; 132 int i; 133 Convert(int x) { i = x; } 134 Convert(float x) { f = x; } 135 int AsInt() { return i; } 136 float AsFloat() { return f; } 137 }; 138 139 inline const int AsInteger(const float f) { 140 Convert u(f); 141 return u.AsInt(); 142 } 143 144 inline const float AsFloat(const int i) { 145 Convert u(i); 146 return u.AsFloat(); 147 } 148 149 const long int kOneAsInteger = AsInteger(1.0f); 150 const float kScaleUp = float(0x00800000); 151 const float kScaleDown = 1.0f / kScaleUp; 152 153 inline float inline_quick_sqrt(float x) { 154 int i; 155 i = (AsInteger(x) >> 1) + (kOneAsInteger >> 1); 156 return AsFloat(i); 157 } 158 159 inline float inline_sqrt(float x) { 160 float y; 161 y = inline_quick_sqrt(x); 162 y = (y * y + x) / (2.0f * y); 163 y = (y * y + x) / (2.0f * y); 164 return y; 165 } 166 167 // takes a -0..1+ color, clamps it to 0..1 and maps it to 0..255 integer 168 inline uint32_t Clamp255(float x) { 169 if (x < 0.0f) { 170 x = 0.0f; 171 } else if (x > 1.0f) { 172 x = 1.0f; 173 } 174 return static_cast<uint32_t>(x * 255.0f); 175 } 176 } // namespace 177 178 179 // The main object that runs the Earth demo. 180 class Planet { 181 public: 182 Planet(); 183 virtual ~Planet(); 184 // Runs a tick of the simulations, update 2D output. 185 void Update(); 186 // Handle event from user, or message from JS. 187 void HandleEvent(PSEvent* ps_event); 188 189 private: 190 // Methods prefixed with 'w' are run on worker threads. 191 uint32_t* wGetAddr(int x, int y); 192 void wRenderPixelSpan(int x0, int x1, int y); 193 void wMakeRect(int r, int *x, int *y, int *w, int *h); 194 void wRenderRect(int x0, int y0, int x1, int y1); 195 void wRenderRegion(int region); 196 static void wRenderRegionEntry(int region, void *thiz); 197 198 // These methods are only called by the main thread. 199 void CacheCalcs(); 200 void SetPlanetXYZR(float x, float y, float z, float r); 201 void SetPlanetPole(float x, float y, float z); 202 void SetPlanetEquator(float x, float y, float z); 203 void SetPlanetSpin(float x, float y); 204 void SetEyeXYZ(float x, float y, float z); 205 void SetLightXYZ(float x, float y, float z); 206 void SetAmbientRGB(float r, float g, float b); 207 void SetDiffuseRGB(float r, float g, float b); 208 void SetZoom(float zoom); 209 void SetLight(float zoom); 210 void SetTexture(const std::string& name, int width, int height, 211 uint32_t* pixels); 212 213 void Reset(); 214 void RequestTextures(); 215 void UpdateSim(); 216 void Render(); 217 void Draw(); 218 void StartBenchmark(); 219 void EndBenchmark(); 220 // Post a small key-value message to update JS. 221 void PostUpdateMessage(const char* message_name, double value); 222 223 // User Interface settings. These settings are controlled via html 224 // controls or via user input. 225 float ui_light_; 226 float ui_zoom_; 227 float ui_spin_x_; 228 float ui_spin_y_; 229 230 // Various settings for position & orientation of planet. Do not change 231 // these variables, instead use SetPlanet*() functions. 232 float planet_radius_; 233 float planet_spin_x_; 234 float planet_spin_y_; 235 float planet_x_, planet_y_, planet_z_; 236 float planet_pole_x_, planet_pole_y_, planet_pole_z_; 237 float planet_equator_x_, planet_equator_y_, planet_equator_z_; 238 239 // Observer's eye. Do not change these variables, instead use SetEyeXYZ(). 240 float eye_x_, eye_y_, eye_z_; 241 242 // Light position, ambient and diffuse settings. Do not change these 243 // variables, instead use SetLightXYZ(), SetAmbientRGB() and SetDiffuseRGB(). 244 float light_x_, light_y_, light_z_; 245 float diffuse_r_, diffuse_g_, diffuse_b_; 246 float ambient_r_, ambient_g_, ambient_b_; 247 248 // Cached calculations. Do not change these variables - they are updated by 249 // CacheCalcs() function. 250 float planet_xyz_; 251 float planet_pole_x_equator_x_; 252 float planet_pole_x_equator_y_; 253 float planet_pole_x_equator_z_; 254 float planet_radius2_; 255 float planet_one_over_radius_; 256 float eye_xyz_; 257 258 // Source texture (earth map). 259 Texture* base_tex_; 260 Texture* night_tex_; 261 int width_for_tex_; 262 int height_for_tex_; 263 264 // Quick ArcCos helper. 265 ArcCosine acos_; 266 267 // Misc. 268 PSContext2D_t* ps_context_; 269 int num_threads_; 270 ThreadPool* workers_; 271 bool benchmarking_; 272 int benchmark_frame_counter_; 273 double benchmark_start_time_; 274 double benchmark_end_time_; 275 }; 276 277 278 void Planet::RequestTextures() { 279 // Request a set of images from JS. After images are loaded by JS, a 280 // message from JS -> NaCl will arrive containing the pixel data. See 281 // HandleMessage() method in this file. 282 pp::VarDictionary message; 283 message.Set("message", "request_textures"); 284 pp::VarArray names; 285 names.Set(0, "earth.jpg"); 286 names.Set(1, "earthnight.jpg"); 287 message.Set("names", names); 288 PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), message.pp_var()); 289 } 290 291 void Planet::Reset() { 292 // Reset has to first fill in all variables with valid floats, so 293 // CacheCalcs() doesn't potentially propagate NaNs when calling Set*() 294 // functions further below. 295 planet_radius_ = 1.0f; 296 planet_spin_x_ = 0.0f; 297 planet_spin_y_ = 0.0f; 298 planet_x_ = 0.0f; 299 planet_y_ = 0.0f; 300 planet_z_ = 0.0f; 301 planet_pole_x_ = 0.0f; 302 planet_pole_y_ = 0.0f; 303 planet_pole_z_ = 0.0f; 304 planet_equator_x_ = 0.0f; 305 planet_equator_y_ = 0.0f; 306 planet_equator_z_ = 0.0f; 307 eye_x_ = 0.0f; 308 eye_y_ = 0.0f; 309 eye_z_ = 0.0f; 310 light_x_ = 0.0f; 311 light_y_ = 0.0f; 312 light_z_ = 0.0f; 313 diffuse_r_ = 0.0f; 314 diffuse_g_ = 0.0f; 315 diffuse_b_ = 0.0f; 316 ambient_r_ = 0.0f; 317 ambient_g_ = 0.0f; 318 ambient_b_ = 0.0f; 319 planet_xyz_ = 0.0f; 320 planet_pole_x_equator_x_ = 0.0f; 321 planet_pole_x_equator_y_ = 0.0f; 322 planet_pole_x_equator_z_ = 0.0f; 323 planet_radius2_ = 0.0f; 324 planet_one_over_radius_ = 0.0f; 325 eye_xyz_ = 0.0f; 326 ui_zoom_ = 14.0f; 327 ui_light_ = 1.0f; 328 ui_spin_x_ = 0.01f; 329 ui_spin_y_ = 0.0f; 330 331 // Set up reasonable default values. 332 SetPlanetXYZR(0.0f, 0.0f, 48.0f, 4.0f); 333 SetEyeXYZ(0.0f, 0.0f, -ui_zoom_); 334 SetLightXYZ(-60.0f, -30.0f, 0.0f); 335 SetAmbientRGB(0.05f, 0.05f, 0.05f); 336 SetDiffuseRGB(0.8f, 0.8f, 0.8f); 337 SetPlanetPole(0.0f, 1.0f, 0.0f); 338 SetPlanetEquator(1.0f, 0.0f, 0.0f); 339 SetPlanetSpin(kPI / 2.0f, kPI / 2.0f); 340 SetZoom(ui_zoom_); 341 SetLight(ui_light_); 342 343 // Send UI values to JS to reset html sliders. 344 PostUpdateMessage("set_zoom", ui_zoom_); 345 PostUpdateMessage("set_light", ui_light_); 346 } 347 348 349 Planet::Planet() : base_tex_(NULL), night_tex_(NULL), num_threads_(0), 350 benchmarking_(false), benchmark_frame_counter_(0) { 351 352 Reset(); 353 RequestTextures(); 354 // By default, render from the dispatch thread. 355 workers_ = new ThreadPool(num_threads_); 356 PSEventSetFilter(PSE_ALL); 357 ps_context_ = PSContext2DAllocate(PP_IMAGEDATAFORMAT_BGRA_PREMUL); 358 } 359 360 Planet::~Planet() { 361 delete workers_; 362 PSContext2DFree(ps_context_); 363 } 364 365 // Given a region r, derive a rectangle. 366 // This rectangle shouldn't overlap with work being done by other workers. 367 // If multithreading, this function is only called by the worker threads. 368 void Planet::wMakeRect(int r, int *x, int *y, int *w, int *h) { 369 *x = 0; 370 *w = ps_context_->width; 371 *y = r; 372 *h = 1; 373 } 374 375 376 inline uint32_t* Planet::wGetAddr(int x, int y) { 377 return ps_context_->data + x + y * ps_context_->stride / sizeof(uint32_t); 378 } 379 380 // This is the meat of the ray tracer. Given a pixel span (x0, x1) on 381 // scanline y, shoot rays into the scene and render what they hit. Use 382 // scanline coherence to do a few optimizations 383 void Planet::wRenderPixelSpan(int x0, int x1, int y) { 384 if (!base_tex_ || !night_tex_) 385 return; 386 const int kColorBlack = MakeBGRA(0, 0, 0, 0xFF); 387 float width = ps_context_->width; 388 float height = ps_context_->height; 389 float min_dim = width < height ? width : height; 390 float offset_x = width < height ? 0 : (width - min_dim) * 0.5f; 391 float offset_y = width < height ? (height - min_dim) * 0.5f : 0; 392 float y0 = eye_y_; 393 float z0 = eye_z_; 394 float y1 = (static_cast<float>(y - offset_y) / min_dim) * 2.0f - 1.0f; 395 float z1 = 0.0f; 396 float dy = (y1 - y0); 397 float dz = (z1 - z0); 398 float dy_dy_dz_dz = dy * dy + dz * dz; 399 float two_dy_y0_y_two_dz_z0_z = 2.0f * dy * (y0 - planet_y_) + 400 2.0f * dz * (z0 - planet_z_); 401 float planet_xyz_eye_xyz = planet_xyz_ + eye_xyz_; 402 float y_y0_z_z0 = planet_y_ * y0 + planet_z_ * z0; 403 float oowidth = 1.0f / min_dim; 404 uint32_t* pixels = this->wGetAddr(x0, y); 405 for (int x = x0; x <= x1; ++x) { 406 // scan normalized screen -1..1 407 float x1 = (static_cast<float>(x - offset_x) * oowidth) * 2.0f - 1.0f; 408 // eye 409 float x0 = eye_x_; 410 // delta from screen to eye 411 float dx = (x1 - x0); 412 // build a, b, c 413 float a = dx * dx + dy_dy_dz_dz; 414 float b = 2.0f * dx * (x0 - planet_x_) + two_dy_y0_y_two_dz_z0_z; 415 float c = planet_xyz_eye_xyz + 416 -2.0f * (planet_x_ * x0 + y_y0_z_z0) - (planet_radius2_); 417 // calculate discriminant 418 float disc = b * b - 4.0f * a * c; 419 420 // Did ray hit the sphere? 421 if (disc < 0.0f) { 422 *pixels = kColorBlack; 423 ++pixels; 424 continue; 425 } 426 427 // calc parametric t value 428 float t = (-b - inline_sqrt(disc)) / (2.0f * a); 429 float px = x0 + t * dx; 430 float py = y0 + t * dy; 431 float pz = z0 + t * dz; 432 float nx = (px - planet_x_) * planet_one_over_radius_; 433 float ny = (py - planet_y_) * planet_one_over_radius_; 434 float nz = (pz - planet_z_) * planet_one_over_radius_; 435 436 // Misc raytrace calculations. 437 float Lx = (light_x_ - px); 438 float Ly = (light_y_ - py); 439 float Lz = (light_z_ - pz); 440 float Lq = 1.0f / inline_quick_sqrt(Lx * Lx + Ly * Ly + Lz * Lz); 441 Lx *= Lq; 442 Ly *= Lq; 443 Lz *= Lq; 444 float d = (Lx * nx + Ly * ny + Lz * nz); 445 float pr = (diffuse_r_ * d) + ambient_r_; 446 float pg = (diffuse_g_ * d) + ambient_g_; 447 float pb = (diffuse_b_ * d) + ambient_b_; 448 float ds = -(nx * planet_pole_x_ + 449 ny * planet_pole_y_ + 450 nz * planet_pole_z_); 451 float ang = acos_.TableLerp(ds); 452 float v = ang * kOneOverPI; 453 float dp = planet_equator_x_ * nx + 454 planet_equator_y_ * ny + 455 planet_equator_z_ * nz; 456 float w = dp / sin(ang); 457 if (w > 1.0f) w = 1.0f; 458 if (w < -1.0f) w = -1.0f; 459 float th = acos_.TableLerp(w) * kOneOver2PI; 460 float dps = planet_pole_x_equator_x_ * nx + 461 planet_pole_x_equator_y_ * ny + 462 planet_pole_x_equator_z_ * nz; 463 float u; 464 if (dps < 0.0f) 465 u = th; 466 else 467 u = 1.0f - th; 468 469 // Look up daylight texel. 470 int tx = static_cast<int>(u * base_tex_->width); 471 int ty = static_cast<int>(v * base_tex_->height); 472 int offset = tx + ty * base_tex_->width; 473 uint32_t base_texel = base_tex_->pixels[offset]; 474 float tr = ExtractR(base_texel); 475 float tg = ExtractG(base_texel); 476 float tb = ExtractB(base_texel); 477 478 float ipr = 1.0f - pr; 479 if (ipr < 0.0f) ipr = 0.0f; 480 float ipg = 1.0f - pg; 481 if (ipg < 0.0f) ipg = 0.0f; 482 float ipb = 1.0f - pb; 483 if (ipb < 0.0f) ipb = 0.0f; 484 485 // Look up night texel. 486 int nix = static_cast<int>(u * night_tex_->width); 487 int niy = static_cast<int>(v * night_tex_->height); 488 int noffset = nix + niy * night_tex_->width; 489 uint32_t night_texel = night_tex_->pixels[noffset]; 490 float nr = ExtractR(night_texel); 491 float ng = ExtractG(night_texel); 492 float nb = ExtractB(night_texel); 493 494 // Final color value is lerp between day and night texels. 495 unsigned int ir = Clamp255(pr * tr + nr * ipr); 496 unsigned int ig = Clamp255(pg * tg + ng * ipg); 497 unsigned int ib = Clamp255(pb * tb + nb * ipb); 498 499 unsigned int color = MakeBGRA(ib, ig, ir, 0xFF); 500 501 *pixels = color; 502 ++pixels; 503 } 504 } 505 506 // Renders a rectangular area of the screen, scan line at a time 507 void Planet::wRenderRect(int x, int y, int w, int h) { 508 for (int j = y; j < (y + h); ++j) { 509 this->wRenderPixelSpan(x, x + w - 1, j); 510 } 511 } 512 513 // If multithreading, this function is only called by the worker threads. 514 void Planet::wRenderRegion(int region) { 515 // convert region # into x0, y0, x1, y1 rectangle 516 int x, y, w, h; 517 wMakeRect(region, &x, &y, &w, &h); 518 // render this rectangle 519 wRenderRect(x, y, w, h); 520 } 521 522 // Entry point for worker thread. Can't pass a member function around, so we 523 // have to do this little round-about. 524 void Planet::wRenderRegionEntry(int region, void* thiz) { 525 static_cast<Planet*>(thiz)->wRenderRegion(region); 526 } 527 528 // Renders the planet, dispatching the work to multiple threads. 529 void Planet::Render() { 530 workers_->Dispatch(ps_context_->height, wRenderRegionEntry, this); 531 } 532 533 // Pre-calculations to make inner loops faster. 534 void Planet::CacheCalcs() { 535 planet_xyz_ = planet_x_ * planet_x_ + 536 planet_y_ * planet_y_ + 537 planet_z_ * planet_z_; 538 planet_radius2_ = planet_radius_ * planet_radius_; 539 planet_one_over_radius_ = 1.0f / planet_radius_; 540 eye_xyz_ = eye_x_ * eye_x_ + eye_y_ * eye_y_ + eye_z_ * eye_z_; 541 // spin vector from center->equator 542 planet_equator_x_ = cos(planet_spin_x_); 543 planet_equator_y_ = 0.0f; 544 planet_equator_z_ = sin(planet_spin_x_); 545 546 // cache cross product of pole & equator 547 planet_pole_x_equator_x_ = planet_pole_y_ * planet_equator_z_ - 548 planet_pole_z_ * planet_equator_y_; 549 planet_pole_x_equator_y_ = planet_pole_z_ * planet_equator_x_ - 550 planet_pole_x_ * planet_equator_z_; 551 planet_pole_x_equator_z_ = planet_pole_x_ * planet_equator_y_ - 552 planet_pole_y_ * planet_equator_x_; 553 } 554 555 void Planet::SetPlanetXYZR(float x, float y, float z, float r) { 556 planet_x_ = x; 557 planet_y_ = y; 558 planet_z_ = z; 559 planet_radius_ = r; 560 CacheCalcs(); 561 } 562 563 void Planet::SetEyeXYZ(float x, float y, float z) { 564 eye_x_ = x; 565 eye_y_ = y; 566 eye_z_ = z; 567 CacheCalcs(); 568 } 569 570 void Planet::SetLightXYZ(float x, float y, float z) { 571 light_x_ = x; 572 light_y_ = y; 573 light_z_ = z; 574 CacheCalcs(); 575 } 576 577 void Planet::SetAmbientRGB(float r, float g, float b) { 578 ambient_r_ = r; 579 ambient_g_ = g; 580 ambient_b_ = b; 581 CacheCalcs(); 582 } 583 584 void Planet::SetDiffuseRGB(float r, float g, float b) { 585 diffuse_r_ = r; 586 diffuse_g_ = g; 587 diffuse_b_ = b; 588 CacheCalcs(); 589 } 590 591 void Planet::SetPlanetPole(float x, float y, float z) { 592 planet_pole_x_ = x; 593 planet_pole_y_ = y; 594 planet_pole_z_ = z; 595 CacheCalcs(); 596 } 597 598 void Planet::SetPlanetEquator(float x, float y, float z) { 599 // This is really over-ridden by spin at the momenent. 600 planet_equator_x_ = x; 601 planet_equator_y_ = y; 602 planet_equator_z_ = z; 603 CacheCalcs(); 604 } 605 606 void Planet::SetPlanetSpin(float x, float y) { 607 planet_spin_x_ = x; 608 planet_spin_y_ = y; 609 CacheCalcs(); 610 } 611 612 // Run a simple sim to spin the planet. Update loop is run once per frame. 613 // Called from the main thread only and only when the worker threads are idle. 614 void Planet::UpdateSim() { 615 float x = planet_spin_x_ + ui_spin_x_; 616 float y = planet_spin_y_ + ui_spin_y_; 617 // keep in nice range 618 if (x > (kPI * 2.0f)) 619 x = x - kPI * 2.0f; 620 else if (x < (-kPI * 2.0f)) 621 x = x + kPI * 2.0f; 622 if (y > (kPI * 2.0f)) 623 y = y - kPI * 2.0f; 624 else if (y < (-kPI * 2.0f)) 625 y = y + kPI * 2.0f; 626 SetPlanetSpin(x, y); 627 } 628 629 void Planet::StartBenchmark() { 630 // For more consistent benchmark numbers, reset to default state. 631 Reset(); 632 printf("Benchmark started...\n"); 633 benchmark_frame_counter_ = kFramesToBenchmark; 634 benchmarking_ = true; 635 benchmark_start_time_ = getseconds(); 636 } 637 638 void Planet::EndBenchmark() { 639 benchmark_end_time_ = getseconds(); 640 printf("Benchmark ended... time: %2.5f\n", 641 benchmark_end_time_ - benchmark_start_time_); 642 benchmarking_ = false; 643 benchmark_frame_counter_ = 0; 644 double total_time = benchmark_end_time_ - benchmark_start_time_; 645 // Send benchmark result to JS. 646 PostUpdateMessage("benchmark_result", total_time); 647 } 648 649 void Planet::SetZoom(float zoom) { 650 ui_zoom_ = std::min(kZoomMax, std::max(kZoomMin, zoom)); 651 SetEyeXYZ(0.0f, 0.0f, -ui_zoom_); 652 } 653 654 void Planet::SetLight(float light) { 655 ui_light_ = std::min(kLightMax, std::max(kLightMin, light)); 656 SetDiffuseRGB(0.8f * ui_light_, 0.8f * ui_light_, 0.8f * ui_light_); 657 SetAmbientRGB(0.4f * ui_light_, 0.4f * ui_light_, 0.4f * ui_light_); 658 } 659 660 void Planet::SetTexture(const std::string& name, int width, int height, 661 uint32_t* pixels) { 662 if (pixels) { 663 if (name == "earth.jpg") { 664 delete base_tex_; 665 base_tex_ = new Texture(width, height, pixels); 666 } else if (name == "earthnight.jpg") { 667 delete night_tex_; 668 night_tex_ = new Texture(width, height, pixels); 669 } 670 } 671 } 672 673 // Handle input events from the user and messages from JS. 674 void Planet::HandleEvent(PSEvent* ps_event) { 675 // Give the 2D context a chance to process the event. 676 if (0 != PSContext2DHandleEvent(ps_context_, ps_event)) 677 return; 678 if (ps_event->type == PSE_INSTANCE_HANDLEINPUT) { 679 // Convert Pepper Simple event to a PPAPI C++ event 680 pp::InputEvent event(ps_event->as_resource); 681 switch (event.GetType()) { 682 case PP_INPUTEVENT_TYPE_KEYDOWN: { 683 pp::KeyboardInputEvent key(event); 684 uint32_t key_code = key.GetKeyCode(); 685 if (key_code == 84) // 't' key 686 if (!benchmarking_) 687 StartBenchmark(); 688 break; 689 } 690 case PP_INPUTEVENT_TYPE_MOUSEMOVE: 691 case PP_INPUTEVENT_TYPE_MOUSEDOWN: { 692 pp::MouseInputEvent mouse = pp::MouseInputEvent(event); 693 if (mouse.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) { 694 PP_Point delta = mouse.GetMovement(); 695 float delta_x = static_cast<float>(delta.x); 696 float delta_y = static_cast<float>(delta.y); 697 float spin_x = std::min(4.0f, std::max(-4.0f, delta_x * 0.5f)); 698 float spin_y = std::min(4.0f, std::max(-4.0f, delta_y * 0.5f)); 699 ui_spin_x_ = spin_x / 100.0f; 700 ui_spin_y_ = spin_y / 100.0f; 701 } 702 break; 703 } 704 case PP_INPUTEVENT_TYPE_WHEEL: { 705 pp::WheelInputEvent wheel = pp::WheelInputEvent(event); 706 PP_FloatPoint ticks = wheel.GetTicks(); 707 SetZoom(ui_zoom_ + (ticks.x + ticks.y) * kWheelSpeed); 708 // Update html slider by sending update message to JS. 709 PostUpdateMessage("set_zoom", ui_zoom_); 710 break; 711 } 712 default: 713 break; 714 } 715 } else if (ps_event->type == PSE_INSTANCE_HANDLEMESSAGE) { 716 // Convert Pepper Simple message to PPAPI C++ vars 717 pp::Var var(ps_event->as_var); 718 if (var.is_dictionary()) { 719 pp::VarDictionary dictionary(var); 720 std::string message = dictionary.Get("message").AsString(); 721 if (message == "run benchmark" && !benchmarking_) { 722 StartBenchmark(); 723 } else if (message == "set_light") { 724 SetLight(static_cast<float>(dictionary.Get("value").AsDouble())); 725 } else if (message == "set_zoom") { 726 SetZoom(static_cast<float>(dictionary.Get("value").AsDouble())); 727 } else if (message == "set_threads") { 728 int threads = dictionary.Get("value").AsInt(); 729 delete workers_; 730 workers_ = new ThreadPool(threads); 731 } else if (message == "texture") { 732 std::string name = dictionary.Get("name").AsString(); 733 int width = dictionary.Get("width").AsInt(); 734 int height = dictionary.Get("height").AsInt(); 735 pp::VarArrayBuffer array_buffer(dictionary.Get("data")); 736 if (!name.empty() && !array_buffer.is_null()) { 737 if (width > 0 && height > 0) { 738 uint32_t* pixels = static_cast<uint32_t*>(array_buffer.Map()); 739 SetTexture(name, width, height, pixels); 740 array_buffer.Unmap(); 741 } 742 } 743 } 744 } else { 745 printf("Handle message unknown type: %s\n", var.DebugString().c_str()); 746 } 747 } 748 } 749 750 // PostUpdateMessage() helper function for sending small messages to JS. 751 void Planet::PostUpdateMessage(const char* message_name, double value) { 752 pp::VarDictionary message; 753 message.Set("message", message_name); 754 message.Set("value", value); 755 PSInterfaceMessaging()->PostMessage(PSGetInstanceId(), message.pp_var()); 756 } 757 758 void Planet::Update() { 759 // When benchmarking is running, don't update display via 760 // PSContext2DSwapBuffer() - vsync is enabled by default, and will throttle 761 // the benchmark results. 762 PSContext2DGetBuffer(ps_context_); 763 if (NULL == ps_context_->data) 764 return; 765 766 do { 767 UpdateSim(); 768 Render(); 769 if (!benchmarking_) break; 770 --benchmark_frame_counter_; 771 } while (benchmark_frame_counter_ > 0); 772 if (benchmarking_) 773 EndBenchmark(); 774 775 PSContext2DSwapBuffer(ps_context_); 776 } 777 778 779 // Starting point for the module. We do not use main since it would 780 // collide with main in libppapi_cpp. 781 int example_main(int argc, char* argv[]) { 782 Planet earth; 783 while (true) { 784 PSEvent* ps_event; 785 // Consume all available events 786 while ((ps_event = PSEventTryAcquire()) != NULL) { 787 earth.HandleEvent(ps_event); 788 PSEventRelease(ps_event); 789 } 790 // Do simulation, render and present. 791 earth.Update(); 792 } 793 794 return 0; 795 } 796 797 // Register the function to call once the Instance Object is initialized. 798 // see: pappi_simple/ps_main.h 799 PPAPI_SIMPLE_REGISTER_MAIN(example_main); 800