blob: 71854df9969b5fb284c717a09ebf27dc4e70cc9b [file] [log] [blame]
/*
* Copyright (c) 2011 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
// NaCl Earth demo
// Ray trace planet Earth
#include "native_client/tests/earth/earth.h"
#include <assert.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define HAVE_THREADS 1
#include "native_client/common/worker.h"
// print/debug messages
static void InfoPrintf(const char *fmt, ...) {
va_list argptr;
va_start (argptr, fmt);
vfprintf (stderr, fmt, argptr);
va_end (argptr);
fflush(stderr);
}
extern "C" void DebugPrintf(const char *fmt, ...) {
va_list argptr;
fprintf (stderr, "@@@ EARTH ");
va_start (argptr, fmt);
vfprintf (stderr, fmt, argptr);
va_end (argptr);
fflush(stderr);
}
// global properties
const float kPI = M_PI;
const float kOneOverPI = 1.0f / kPI;
const float kOneOver2PI = 1.0f / (2.0f * kPI);
const float kOneOver255 = 1.0f / 255.0f;
const int kArcCosineTableSize = 4096;
const float kMaxWindow = 4096;
const int kEarthTextureWidth = 1024;
const int kEarthTextureHeight = 512;
const int kMaxFrames = 1000000;
const int kRegionRatio = 8;
int g_window_width = 512;
int g_window_height = 512;
int g_num_frames = 300;
bool g_ask_sysconf = true;
int g_num_threads = 4; // possibly overridden by sysconf()
int g_num_regions = 4; // possibly overridden by sysconf()
bool g_multi_threading = false; // can be overridden on cmd line
int g_frame_checksum = 0; // used for nacl module testing
// seed for rand_r() - we only call rand_r from main thread.
static unsigned int g_seed = 0xC0DE533D;
// random number helper
inline unsigned char rand255() {
return static_cast<unsigned char>(rand_r(&g_seed) & 255);
}
// random number helper
inline float frand() {
return (static_cast<float>(rand_r(&g_seed)) / static_cast<float>(RAND_MAX));
}
// build a packed color
inline uint32_t MakeRGBA(uint32_t r, uint32_t g, uint32_t b, uint32_t a) {
return (((a) << 24) | ((r) << 16) | ((g) << 8) | (b));
}
// extraction routines
inline float ExtractR(uint32_t c) {
return static_cast<float>(c & 0xFF) * kOneOver255;
}
inline float ExtractG(uint32_t c) {
return static_cast<float>((c & 0xFF00) >> 8) * kOneOver255;
}
inline float ExtractB(uint32_t c) {
return static_cast<float>((c & 0xFF0000) >> 16) * kOneOver255;
}
// simple container for earth texture
struct Texture {
int width, height;
unsigned int pixels[kEarthTextureWidth * kEarthTextureHeight];
};
// compile our texture straight in to avoid runtime filesystem
Texture g_earth = {
kEarthTextureWidth, kEarthTextureHeight, {
#include "native_client/tests/earth/earth_image.inc"
}
};
struct Surface {
int width, height, pitch;
uint32_t *pixels;
Surface(uint32_t *pix, int w, int h) {
width = w;
height = h;
pitch = w;
pixels = pix;
}
};
struct ArcCosine {
// slightly larger table so we can interpolate beyond table size
float table[kArcCosineTableSize + 2];
float TableLerp(float x);
ArcCosine();
~ArcCosine() { ; }
};
ArcCosine::ArcCosine() {
// build a slightly larger table to allow for numeric imprecision
for (int i = 0; i < (kArcCosineTableSize + 2); ++i) {
float f = static_cast<float>(i) / kArcCosineTableSize;
f = f * 2.0f - 1.0f;
table[i] = acos(f);
}
}
// looks up acos(f) using a table and lerping between entries
// (it is expected that input f is between -1 and 1)
float ArcCosine::TableLerp(float f) {
float x = (f + 1.0f) * 0.5f;
x = x * kArcCosineTableSize;
int ix = static_cast<int>(x);
float fx = static_cast<float>(ix);
float dx = x - fx;
float af = table[ix];
float af2 = table[ix + 1];
float a = af + (af2 - af) * dx;
return a;
}
// takes a -0..1+ color, clamps it to 0..1 and maps it to 0..255 integer
inline unsigned int Clamp255(float x) {
if (x < 0.0f) {
x = 0.0f;
} else if (x > 1.0f) {
x = 1.0f;
}
return (unsigned int)(x * 255.0f);
}
// Planet class holds information and functionality needed to render
// a ray-traced planet into an raw pixel surface
class Planet {
float planet_radius_;
float planet_spin_;
float planet_x_, planet_y_, planet_z_;
float planet_pole_x_, planet_pole_y_, planet_pole_z_;
float planet_equator_x_, planet_equator_y_, planet_equator_z_;
float eye_x_, eye_y_, eye_z_;
float light_x_, light_y_, light_z_;
float diffuse_r_, diffuse_g_, diffuse_b_;
float ambient_r_, ambient_g_, ambient_b_;
// cached calculations
float planet_xyz_;
float planet_pole_x_equator_x_;
float planet_pole_x_equator_y_;
float planet_pole_x_equator_z_;
float planet_radius2_;
float planet_one_over_radius_;
float eye_xyz_;
// misc
Texture *tex_;
Surface surface_;
ArcCosine acos_;
int num_regions_;
WorkerThreadManager *workers_;
volatile bool exiting_;
bool rendering_;
public:
// methods prefixed with 'w' are only called by worker threads!
// (unless MULTI_THREADING is false)
uint32_t* wGetAddr(int x, int y);
void wRenderPixelSpan(int x0, int x1, int y);
void wMakeRect(int r, int *x, int *y, int *w, int *h);
void wRenderRect(int x0, int y0, int x1, int y1);
void wWorkerThreadEntry();
// these methods are only called by the main thread
void CacheCalcs();
void SetPlanetXYZR(float x, float y, float z, float r);
void SetPlanetPole(float x, float y, float z);
void SetPlanetEquator(float x, float y, float z);
void SetPlanetSpin(float a);
void SetEyeXYZ(float x, float y, float z);
void SetLightXYZ(float x, float y, float z);
void SetAmbientRGB(float r, float g, float b);
void SetDiffuseRGB(float r, float g, float b);
void SetSurface(Surface surface);
bool CreateWorkerThreads(int num);
void UpdateSim();
void ParallelRender();
void ParallelRenderSync();
void SequentialRender();
void Render();
void Sync();
Planet(int numRegions, bool multithreading, Texture *tex);
~Planet();
};
// Given a region r, derive a rectangle. Currently this function
// slices the main output buffer into equal sized rows.
// This function is used to convert a mutex guarded counter into
// a rectangular region for a given worker thread to process.
// This rectangle shouldn't overlap with work being done by other workers.
// If multithreading, this function is only called by the worker threads.
void Planet::wMakeRect(int r, int *x, int *y, int *w, int *h) {
int dy = surface_.height / num_regions_;
*x = 0;
*w = surface_.width;
*y = r * dy;
*h = dy;
}
inline uint32_t* Planet::wGetAddr(int x, int y) {
assert(surface_.pixels);
return (surface_.pixels + y * surface_.pitch) + x;
}
union Convert {
float f;
int i;
Convert(int x) { i = x; }
Convert(float x) { f = x; }
const int asInt() { return i; }
const float asFloat() { return f; }
};
inline const int AsInteger(const float f) {
Convert u(f);
return u.asInt();
}
inline const float AsFloat(const int i) {
Convert u(i);
return u.asFloat();
}
const long int kOneAsInteger = AsInteger(1.0f);
const float kScaleUp = float(0x00800000);
const float kScaleDown = 1.0f / kScaleUp;
inline float inline_quick_sqrt(float x) {
int i;
i = (AsInteger(x) >> 1) + (kOneAsInteger >> 1);
return AsFloat(i);
}
inline float inline_sqrt(float x) {
float y;
y = inline_quick_sqrt(x);
y = (y * y + x) / (2.0f * y);
y = (y * y + x) / (2.0f * y);
return y;
}
// This is the meat of the ray tracer. Given a pixel span (x0, x1) on
// scanline y, shoot rays into the scene and render what they hit. Use
// scanline coherence to do a few optimizations
void Planet::wRenderPixelSpan(int x0, int x1, int y) {
const int kColorBlack = MakeRGBA(0, 0, 0, 0xFF);
float y0 = eye_y_;
float z0 = eye_z_;
float y1 = (static_cast<float>(y) / surface_.height) * 2.0f - 1.0f;
float z1 = 0.0f;
float dy = (y1 - y0);
float dz = (z1 - z0);
float dy_dy_dz_dz = dy * dy + dz * dz;
float two_dy_y0_y_two_dz_z0_z = 2.0f * dy * (y0 - planet_y_) +
2.0f * dz * (z0 - planet_z_);
float planet_xyz_eye_xyz = planet_xyz_ + eye_xyz_;
float y_y0_z_z0 = planet_y_ * y0 + planet_z_ * z0;
float oowidth = 1.0f / surface_.width;
uint32_t *pixels = this->wGetAddr(x0, y);
for (int x = x0; x <= x1; ++x) {
// scan normalized screen -1..1
float x1 = (static_cast<float>(x) * oowidth) * 2.0f - 1.0f;
// eye
float x0 = eye_x_;
// delta from screen to eye
float dx = (x1 - x0);
// build a, b, c
float a = dx * dx + dy_dy_dz_dz;
float b = 2.0f * dx * (x0 - planet_x_) + two_dy_y0_y_two_dz_z0_z;
float c = planet_xyz_eye_xyz +
-2.0f * (planet_x_ * x0 + y_y0_z_z0) - (planet_radius2_);
// calculate discriminant
float disc = b * b - 4.0f * a * c;
// did ray hit the sphere?
if (disc < 0.0f) {
*pixels = kColorBlack;
++pixels;
continue;
}
// calc parametric t value
float t = (-b - inline_sqrt(disc)) / (2.0f * a);
float px = x0 + t * dx;
float py = y0 + t * dy;
float pz = z0 + t * dz;
float nx = (px - planet_x_) * planet_one_over_radius_;
float ny = (py - planet_y_) * planet_one_over_radius_;
float nz = (pz - planet_z_) * planet_one_over_radius_;
float Lx = (light_x_ - px);
float Ly = (light_y_ - py);
float Lz = (light_z_ - pz);
float Lq = 1.0f / inline_quick_sqrt(Lx * Lx + Ly * Ly + Lz * Lz);
Lx *= Lq;
Ly *= Lq;
Lz *= Lq;
float d = (Lx * nx + Ly * ny + Lz * nz);
float pr = (diffuse_r_ * d) + ambient_r_;
float pg = (diffuse_g_ * d) + ambient_g_;
float pb = (diffuse_b_ * d) + ambient_b_;
float ds = -(nx * planet_pole_x_ +
ny * planet_pole_y_ +
nz * planet_pole_z_);
float ang = acos_.TableLerp(ds);
float v = ang * kOneOverPI;
float dp = planet_equator_x_ * nx +
planet_equator_y_ * ny +
planet_equator_z_ * nz;
float w = dp / sin(ang);
if (w > 1.0f) w = 1.0f;
if (w < -1.0f) w = -1.0f;
float th = acos_.TableLerp(w) * kOneOver2PI;
float dps = planet_pole_x_equator_x_ * nx +
planet_pole_x_equator_y_ * ny +
planet_pole_x_equator_z_ * nz;
float u;
if (dps < 0.0f)
u = th;
else
u = 1.0f - th;
int tx = static_cast<int>(u * tex_->width);
int ty = static_cast<int>(v * tex_->height);
int offset = tx + ty * tex_->width;
uint32_t texel = tex_->pixels[offset];
float tr = ExtractR(texel);
float tg = ExtractG(texel);
float tb = ExtractB(texel);
unsigned int ir = Clamp255(pr * tr);
unsigned int ig = Clamp255(pg * tg);
unsigned int ib = Clamp255(pb * tb);
unsigned int color = MakeRGBA(ir, ig, ib, 0xFF);
*pixels = color;
++pixels;
}
}
// Renders a rectangular area of the screen, scan line at a time
void Planet::wRenderRect(int x, int y, int w, int h) {
for (int j = y; j < (y + h); ++j) {
this->wRenderPixelSpan(x, x + w - 1, j);
}
}
// Thread entry point Planet::wWorkerThread()
// This is the main loop for the worker thread(s). It waits for work
// by testing a semaphore, which will sleep this thread until work arrives.
// It then grabs a mutex protected counter (which is also decremented)
// and uses this value to determine which subregion this thread should be
// processing. When rendering is finished the worker then pings the main
// thread via PostDone() semaphore.
// If multithreading, this function is only called by the worker threads.
void Planet::wWorkerThreadEntry() {
// we're a 'detached' thread...
// (so we should automagically die when the main thread exits)
while (!exiting_) {
// wait for some work
workers_->WaitWork();
// if main thread is exiting, have worker exit too
if (exiting_) break;
// okay, grab region to work on from worker counter
int region = workers_->DecCounter();
if (region < 0) {
// This indicates we're not sync'ing properly
InfoPrintf("Region value went negative!\n");
exit(-1);
}
// convert region # into x0, y0, x1, y1 rectangle
int x, y, w, h;
this->wMakeRect(region, &x, &y, &w, &h);
// render this rectangle
this->wRenderRect(x, y, w, h);
// let main thread know we've finished a region
workers_->PostDone();
}
}
// Entry point for worker thread. (Can't really pass a member function to
// pthread_create(), so we have to do this little round-about)
// If multithreading, this function is only called by the worker threads.
void* wWorkerThreadEntry(void *args) {
// unpack this pointer
Planet *pPlanet = reinterpret_cast<Planet*>(args);
pPlanet->wWorkerThreadEntry();
return NULL;
}
// Create worker threads and pass along our this pointer.
// If workers_ is NULL, we're running in non-threaded mode.
bool Planet::CreateWorkerThreads(int num) {
if (NULL != workers_) {
return workers_->CreateThreadPool(num, ::wWorkerThreadEntry, this);
} else {
return true;
}
}
// Run a simple sim to spin the planet. Update loop is run once per frame.
// Called from the main thread only and only when the worker threads are idle.
void Planet::UpdateSim() {
float m = planet_spin_ + 0.01f;
// keep in nice range
if (m > (kPI * 2.0f))
m = m - kPI * 2.0f;
SetPlanetSpin(m);
}
// This is the main thread's entry point to dispatch rendering of the planet.
// First, it sets the region counter to its max value. This mutex guarded
// counter is how worker threads determine which region of the diagram they
// should be working on. Then it pings the PostWork semaphore multiple times
// to wake up the sleeping worker threads.
void Planet::ParallelRender() {
// At this point, all worker threads are idle and sleeping
// setup barrier counter before we wake workers
workers_->SetCounter(num_regions_);
rendering_ = true;
// wake up the workers
for (int i = 0; i < num_regions_; ++i) {
workers_->PostWork();
}
// At this point, all worker threads are awake and busy grabbing
// work assignments by reading and decrementing the counter.
}
// ParallelRenderSync will sleep a little by waiting for the workers to
// finish. It does that by waiting on the WaitDone semaphore.
void Planet::ParallelRenderSync() {
// Only wait if rendering is in progress.
if (rendering_) {
// wait for all work to be done
for (int i = 0; i < num_regions_; ++i) {
workers_->WaitDone();
}
// verify that our counter is where we expect it to be
int c = workers_->DecCounter();
if (-1 != c) {
InfoPrintf("We're not syncing correctly! (%d)\n", c);
exit(-1);
}
rendering_ = false;
}
// At this point, all worker threads are idle and sleeping again.
// The main thread is free to muck with shared data, such
// as updating the earth spin in the sim routine.
}
// Performs all rendering from the main thread.
void Planet::SequentialRender() {
this->wRenderRect(0, 0, surface_.width, surface_.height);
}
// Renders the Planet diagram.
// Picks either parallel or sequential rendering implementation.
void Planet::Render() {
if (NULL == workers_) {
this->SequentialRender();
} else {
this->ParallelRender();
}
}
// Waits for a rendering to complete.
void Planet::Sync() {
if (NULL != workers_) {
this->ParallelRenderSync();
}
}
// pre-calculations to make inner loops faster
// these need to be recalculated when values change
void Planet::CacheCalcs() {
planet_xyz_ = planet_x_ * planet_x_ +
planet_y_ * planet_y_ +
planet_z_ * planet_z_;
planet_radius2_ = planet_radius_ * planet_radius_;
planet_one_over_radius_ = 1.0f / planet_radius_;
eye_xyz_ = eye_x_ * eye_x_ + eye_y_ * eye_y_ + eye_z_ * eye_z_;
// spin vector from center->equator
planet_equator_x_ = cos(planet_spin_);
planet_equator_y_ = 0.0f;
planet_equator_z_ = sin(planet_spin_);
// cache cross product of pole & equator
planet_pole_x_equator_x_ = planet_pole_y_ * planet_equator_z_ -
planet_pole_z_ * planet_equator_y_;
planet_pole_x_equator_y_ = planet_pole_z_ * planet_equator_x_ -
planet_pole_x_ * planet_equator_z_;
planet_pole_x_equator_z_ = planet_pole_x_ * planet_equator_y_ -
planet_pole_y_ * planet_equator_x_;
}
void Planet::SetPlanetXYZR(float x, float y, float z, float r) {
planet_x_ = x;
planet_y_ = y;
planet_z_ = z;
planet_radius_ = r;
CacheCalcs();
}
void Planet::SetEyeXYZ(float x, float y, float z) {
eye_x_ = x;
eye_y_ = y;
eye_z_ = z;
CacheCalcs();
}
void Planet::SetLightXYZ(float x, float y, float z) {
light_x_ = x;
light_y_ = y;
light_z_ = z;
CacheCalcs();
}
void Planet::SetAmbientRGB(float r, float g, float b) {
ambient_r_ = r;
ambient_g_ = g;
ambient_b_ = b;
CacheCalcs();
}
void Planet::SetDiffuseRGB(float r, float g, float b) {
diffuse_r_ = r;
diffuse_g_ = g;
diffuse_b_ = b;
CacheCalcs();
}
void Planet::SetPlanetPole(float x, float y, float z) {
planet_pole_x_ = x;
planet_pole_y_ = y;
planet_pole_z_ = z;
CacheCalcs();
}
void Planet::SetPlanetEquator(float x, float y, float z) {
// this is really over-ridden by spin at the momenent
planet_equator_x_ = x;
planet_equator_y_ = y;
planet_equator_z_ = z;
CacheCalcs();
}
void Planet::SetPlanetSpin(float a) {
planet_spin_ = a;
CacheCalcs();
}
void Planet::SetSurface(Surface surface) {
surface_ = surface;
}
// Setups and initializes planet data structures.
// Seed planet, eye, and light
Planet::Planet(int numRegions, bool multi, Texture *tex) :
planet_radius_(1.0f),
planet_spin_(0.0f),
planet_x_(0.0f),
planet_y_(0.0f),
planet_z_(0.0f),
planet_pole_x_(0.0f),
planet_pole_y_(0.0f),
planet_pole_z_(0.0f),
planet_equator_x_(0.0f),
planet_equator_y_(0.0f),
planet_equator_z_(0.0f),
eye_x_(0.0f),
eye_y_(0.0f),
eye_z_(0.0f),
light_x_(0.0f),
light_y_(0.0f),
light_z_(0.0f),
diffuse_r_(0.0f),
diffuse_g_(0.0f),
diffuse_b_(0.0f),
ambient_r_(0.0f),
ambient_g_(0.0f),
ambient_b_(0.0f),
planet_xyz_(0.0f),
planet_pole_x_equator_x_(0.0f),
planet_pole_x_equator_y_(0.0f),
planet_pole_x_equator_z_(0.0f),
planet_radius2_(0.0f),
planet_one_over_radius_(0.0f),
eye_xyz_(0.0f),
surface_(NULL, 0, 0) {
num_regions_ = numRegions;
workers_ = multi ? new WorkerThreadManager() : NULL;
tex_ = tex;
exiting_ = false;
rendering_ = false;
this->SetPlanetXYZR(0.0f, 0.0f, 48.0f, 4.0f);
this->SetEyeXYZ(0.0f, 0.0f, -14.0f);
this->SetLightXYZ(-8.0f, -4.0f, 2.0f);
this->SetAmbientRGB(0.4f, 0.4f, 0.4f);
this->SetDiffuseRGB(0.8f, 0.8f, 0.8f);
this->SetPlanetPole(0.0f, 1.0f, 0.0f);
this->SetPlanetEquator(1.0f, 0.0f, 0.0f);
this->SetPlanetSpin(kPI / 2.0f);
}
// Frees up planet resources.
Planet::~Planet() {
if (workers_) {
exiting_ = true;
// wake up the worker threads from their slumber
workers_->PostWorkAll();
workers_->JoinAll();
delete workers_;
}
}
// Clamps input to the max we can realistically support.
static int ClampThreads(int num) {
const int max = 128;
if (num > max) {
return max;
}
return num;
}
static void PrintCredits() {
static const char *credit =
"\n"
"Image Credit:\n"
"\n"
"NASA Goddard Space Flight Center Image by Reto Stöckli (land surface,\n"
"shallow water, clouds). Enhancements by Robert Simmon (ocean color,\n"
"compositing, 3D globes, animation).\n"
"Data and technical support: MODIS Land Group; MODIS Science Data,\n"
"Support Team; MODIS Atmosphere Group; MODIS Ocean Group\n"
"Additional data:\n"
"USGS EROS Data Center (topography); USGS Terrestrial Remote Sensing\n"
"Flagstaff Field Center (Antarctica); Defense Meteorological\n"
"Satellite Program (city lights).\n"
"\n";
InfoPrintf(credit);
}
// If user specifies options on cmd line, parse them
// here and update global settings as needed.
static void ParseCmdLineArgs(int argc, const char *argn[], const char *argv[]) {
// look for cmd line args
PrintCredits();
if (argc > 1) {
for (int i = 1; i < argc; ++i) {
if (argn[i] == strstr(argn[i], "numthreads")) {
int numthreads = atoi(argv[i]);
if (numthreads > 1) {
g_multi_threading = true;
g_num_threads = numthreads;
g_num_regions = numthreads * kRegionRatio;
InfoPrintf("Using %d threads\n", numthreads);
} else {
InfoPrintf("Could not parse numthreads=%s.\n", argv[i]);
}
} else if (argn[i] == strstr(argn[i], "usesysconf")) {
if (argv[i] == strstr(argv[i], "true")) {
g_multi_threading = true;
g_ask_sysconf = true;
} else if (argv[i] == strstr(argv[i], "false")) {
g_multi_threading = false;
} else {
InfoPrintf("Could not parse usesysconf=%s.\n", argv[i]);
}
} else if (argn[i] == strstr(argn[i], "xwidth")) {
int w = atoi(argv[i]);
if ((w > 0) && (w < kMaxWindow)) g_window_width = w;
} else if (argn[i] == strstr(argn[i], "xheight")) {
int h = atoi(argv[i]);
if ((h > 0) && (h < kMaxWindow)) g_window_height = h;
} else if (argn[i] == strstr(argn[i], "frames")) {
int f = atoi(argv[i]);
if ((f > 0) && (f < kMaxFrames)) g_num_frames = f;
} else if (argn[i] == strstr(argn[i], "id")) {
/* ignore id */
} else if (argn[i] == strstr(argn[i], "src")) {
/* ignore src */
} else if (argn[i] == strstr(argn[i], "style")) {
/* ignore style */
} else if (argn[i] == strstr(argn[i], "type")) {
/* ignore type */
} else {
if (argn[i] != strstr(argn[i], "help")) {
InfoPrintf("unknown option %s=%s\n", argn[i], argv[i]);
}
InfoPrintf("Earth Pepper Demo\n"
"usage: numthreads=\"n\" render using n threads.\n"
" usesysconf=true use sysconf to set thread count."
"\n"
" xwidth=\"w\" width of window.\n"
" xheight=\"h\" height of window.\n"
" framecount=\"n\" number of frames.\n"
" help show this screen.\n");
}
}
}
InfoPrintf("Multi-threading %s.\n",
g_multi_threading ? "enabled" : "disabled");
// see if the system can tell us # cpus
if ((g_ask_sysconf) && (g_multi_threading)) {
int ncpu = sysconf(_SC_NPROCESSORS_ONLN);
if (ncpu > 1) {
InfoPrintf("Using %d processors based on sysconf.\n", ncpu);
g_num_threads = ncpu;
g_num_regions = ncpu * kRegionRatio;
}
}
// clamp threads and regions
g_num_threads = ClampThreads(g_num_threads);
g_num_regions = ClampThreads(g_num_regions);
}
Planet *g_planet = NULL;
// Parses cmd line options, initializes surface, runs the demo & shuts down.
extern "C" void Earth_Init(int argc, const char *argn[], const char *argv[]) {
ParseCmdLineArgs(argc, argn, argv);
g_planet = new Planet(g_num_regions, g_multi_threading, &g_earth);
if (!g_planet->CreateWorkerThreads(g_num_threads)) {
DebugPrintf("Earth_Init: thread creation failed. g_num_threads: %d\n",
g_num_threads);
exit(-1);
}
}
extern "C" void Earth_Draw(uint32_t *image_data, int width, int height) {
g_planet->SetSurface(Surface(image_data, width, height));
g_planet->UpdateSim();
g_planet->Render();
}
extern "C" void Earth_Sync() {
g_planet->Sync();
g_planet->SetSurface(Surface(NULL, 0, 0));
}