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