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