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