erebus

annotate liberebus/src/erebus.cc @ 44:a2afaf8af09b

helpful comment
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 18 Sep 2015 06:47:41 +0300
parents 9d6368850fe1
children c4d48a21bc4a
rev   line source
nuclear@2 1 #include <string.h>
nuclear@2 2 #include <limits.h>
nuclear@27 3 #include <algorithm>
nuclear@2 4 #include <chrono>
nuclear@2 5 #include <random>
nuclear@37 6 #ifndef _MSC_VER
nuclear@37 7 #include <alloca.h>
nuclear@37 8 #else
nuclear@37 9 #include <malloc.h>
nuclear@37 10 #endif
nuclear@2 11 #include "erebus.h"
nuclear@17 12 #include "erebus_impl.h"
nuclear@4 13 #include "scene.h"
nuclear@4 14 #include "geomobj.h"
nuclear@5 15 #include "rt.h"
nuclear@2 16
nuclear@8 17 #define INF_SAMPLES (INT_MAX / 2)
nuclear@8 18
nuclear@2 19 using namespace std::chrono;
nuclear@2 20
nuclear@26 21 static void render_block(struct erebus *ctx, Block blk);
nuclear@8 22 static void render_pixel(struct erebus *ctx, int x, int y, int sample);
nuclear@2 23
nuclear@2 24 static std::mt19937 rnd_gen;
nuclear@2 25
nuclear@2 26 extern "C" {
nuclear@2 27
nuclear@2 28 struct erebus *erb_init(void)
nuclear@2 29 {
nuclear@31 30 struct erebus *ctx = 0;
nuclear@2 31 try {
nuclear@2 32 ctx = new struct erebus;
nuclear@2 33 }
nuclear@2 34 catch(...) {
nuclear@2 35 return 0;
nuclear@2 36 }
nuclear@2 37
nuclear@10 38 rnd_gen.seed(time(0));
nuclear@10 39
nuclear@40 40 ctx->rendering = false;
nuclear@4 41 ctx->scn = 0;
nuclear@2 42 ctx->cur_time = 0;
nuclear@26 43 ctx->cur_frame = 0;
nuclear@32 44 ctx->tpool = 0;
nuclear@8 45
nuclear@17 46 erb_setoptf(ctx, ERB_OPT_GAMMA, 2.2);
nuclear@38 47 erb_setopti(ctx, ERB_OPT_MAX_ITER, 8);
nuclear@17 48 erb_setopti(ctx, ERB_OPT_MAX_SAMPLES, INF_SAMPLES);
nuclear@17 49 erb_setopti(ctx, ERB_OPT_NUM_THREADS, -1);
nuclear@15 50
nuclear@15 51 ctx->dbg_nodesel = -1;
nuclear@2 52 return ctx;
nuclear@2 53 }
nuclear@2 54
nuclear@2 55 void erb_destroy(struct erebus *ctx)
nuclear@2 56 {
nuclear@31 57 if(ctx) {
nuclear@31 58 // make sure the threadpool stops BEFORE destroying the framebuffers etc in ctx
nuclear@31 59 delete ctx->tpool;
nuclear@31 60 delete ctx;
nuclear@31 61 }
nuclear@2 62 }
nuclear@2 63
nuclear@40 64 void erb_clear(struct erebus *ctx)
nuclear@40 65 {
nuclear@40 66 erb_end_frame(ctx);
nuclear@40 67 ctx->scn->clear();
nuclear@40 68 }
nuclear@40 69
nuclear@2 70 void erb_setopti(struct erebus *ctx, enum erb_option opt, int val)
nuclear@2 71 {
nuclear@17 72 ctx->options[opt].ival = val;
nuclear@17 73 ctx->options[opt].type = Option::Type::INT;
nuclear@2 74 }
nuclear@17 75
nuclear@2 76 void erb_setoptf(struct erebus *ctx, enum erb_option opt, float val)
nuclear@2 77 {
nuclear@17 78 ctx->options[opt].fval = val;
nuclear@17 79 ctx->options[opt].type = Option::Type::FLOAT;
nuclear@2 80 }
nuclear@17 81
nuclear@2 82 void erb_setoptfv(struct erebus *ctx, enum erb_option opt, float *vec)
nuclear@2 83 {
nuclear@2 84 for(int i=0; i<4; i++) {
nuclear@17 85 ctx->options[opt].vval[i] = vec[i];
nuclear@2 86 }
nuclear@17 87 ctx->options[opt].type = Option::Type::VEC;
nuclear@2 88 }
nuclear@2 89
nuclear@2 90 int erb_getopti(struct erebus *ctx, enum erb_option opt)
nuclear@2 91 {
nuclear@17 92 switch(ctx->options[opt].type) {
nuclear@17 93 case Option::Type::INT:
nuclear@17 94 return ctx->options[opt].ival;
nuclear@17 95 case Option::Type::FLOAT:
nuclear@17 96 return (int)ctx->options[opt].fval;
nuclear@17 97 case Option::Type::VEC:
nuclear@17 98 return (int)ctx->options[opt].vval.x;
nuclear@17 99 }
nuclear@17 100 return 0; // can't happen
nuclear@2 101 }
nuclear@17 102
nuclear@2 103 float erb_getoptf(struct erebus *ctx, enum erb_option opt)
nuclear@2 104 {
nuclear@17 105 switch(ctx->options[opt].type) {
nuclear@17 106 case Option::Type::INT:
nuclear@17 107 return (float)ctx->options[opt].ival;
nuclear@17 108 case Option::Type::FLOAT:
nuclear@17 109 return ctx->options[opt].fval;
nuclear@17 110 case Option::Type::VEC:
nuclear@17 111 return ctx->options[opt].vval.x;
nuclear@17 112 }
nuclear@17 113 return 0.0f; // can't happen
nuclear@2 114 }
nuclear@17 115
nuclear@2 116 float *erb_getoptfv(struct erebus *ctx, enum erb_option opt)
nuclear@2 117 {
nuclear@17 118 switch(ctx->options[opt].type) {
nuclear@17 119 case Option::Type::INT:
nuclear@17 120 {
nuclear@17 121 int ival = ctx->options[opt].ival;
nuclear@17 122 ctx->options[opt].vval = Vector4(ival, ival, ival, ival);
nuclear@17 123 }
nuclear@17 124 break;
nuclear@17 125 case Option::Type::FLOAT:
nuclear@17 126 {
nuclear@17 127 float fval = ctx->options[opt].fval;
nuclear@17 128 ctx->options[opt].vval = Vector4(fval, fval, fval, fval);
nuclear@17 129 }
nuclear@17 130 default:
nuclear@17 131 break;
nuclear@17 132 }
nuclear@17 133
nuclear@17 134 return &ctx->options[opt].vval.x;
nuclear@2 135 }
nuclear@2 136
nuclear@2 137 float *erb_get_framebuffer(struct erebus *ctx)
nuclear@2 138 {
nuclear@2 139 return ctx->fbimg.get_pixels();
nuclear@2 140 }
nuclear@2 141
nuclear@2 142 void erb_begin_frame(struct erebus *ctx, long ms)
nuclear@2 143 {
nuclear@32 144 if(!ctx->tpool) {
nuclear@32 145 int num_threads = erb_getopti(ctx, ERB_OPT_NUM_THREADS);
nuclear@32 146 ctx->tpool = new ThreadPool(num_threads);
nuclear@32 147 }
nuclear@34 148 ctx->tpool->clear_work(); // remove any previously pending jobs
nuclear@32 149
nuclear@26 150 ++ctx->cur_frame;
nuclear@26 151 ctx->cur_sample = 0;
nuclear@2 152 ctx->cur_time = ms;
nuclear@4 153
nuclear@17 154 int xsz = erb_getopti(ctx, ERB_OPT_WIDTH);
nuclear@17 155 int ysz = erb_getopti(ctx, ERB_OPT_HEIGHT);
nuclear@4 156
nuclear@26 157 if(!ctx->fbimg.get_pixels() || ctx->fbimg.get_width() != xsz || ctx->fbimg.get_height() < ysz) {
nuclear@26 158 ctx->fbimg.create(xsz, ysz);
nuclear@26 159 ctx->accum.create(xsz, ysz);
nuclear@26 160 } else {
nuclear@26 161 ctx->fbimg.clear();
nuclear@26 162 ctx->accum.clear();
nuclear@26 163 }
nuclear@15 164
nuclear@17 165 ctx->inv_gamma = 1.0f / erb_getoptf(ctx, ERB_OPT_GAMMA);
nuclear@26 166
nuclear@26 167 ctx->scn->update(ctx->cur_time);
nuclear@40 168 ctx->rendering = true;
nuclear@40 169 }
nuclear@40 170
nuclear@40 171 void erb_end_frame(struct erebus *ctx)
nuclear@40 172 {
nuclear@40 173 if(ctx->tpool) {
nuclear@40 174 ctx->tpool->clear_work();
nuclear@40 175 ctx->tpool->wait();
nuclear@40 176 }
nuclear@40 177 ctx->rendering = false;
nuclear@2 178 }
nuclear@2 179
nuclear@2 180 int erb_render(struct erebus *ctx, long timeout)
nuclear@2 181 {
nuclear@2 182 return erb_render_rect(ctx, 0, 0, ctx->fbimg.get_width(), ctx->fbimg.get_height(), timeout);
nuclear@2 183 }
nuclear@2 184
nuclear@26 185 #define BLKSZ 32
nuclear@26 186
nuclear@2 187 int erb_render_rect(struct erebus *ctx, int x, int y, int width, int height, long timeout)
nuclear@2 188 {
nuclear@40 189 if(!ctx->rendering) {
nuclear@40 190 return 0;
nuclear@40 191 }
nuclear@40 192
nuclear@31 193 while(ctx->tpool->pending()) {
nuclear@26 194 if(timeout > 0) {
nuclear@31 195 long wait_interval = ctx->tpool->wait(timeout);
nuclear@26 196 timeout -= wait_interval;
nuclear@26 197 } else {
nuclear@26 198 return 1;
nuclear@26 199 }
nuclear@26 200 }
nuclear@26 201
nuclear@2 202 if(!width || !height) return -1;
nuclear@2 203
nuclear@26 204 int startx = x;
nuclear@26 205 int endx = x + width;
nuclear@26 206 int endy = y + height;
nuclear@26 207
nuclear@26 208 while(y < endy) {
nuclear@26 209 x = startx;
nuclear@26 210 while(x < endx) {
nuclear@26 211 Block blk;
nuclear@26 212 blk.x = x;
nuclear@26 213 blk.y = y;
nuclear@26 214 blk.width = std::min(BLKSZ, endx - x);
nuclear@26 215 blk.height = std::min(BLKSZ, endy - y);
nuclear@26 216 blk.sample = ctx->cur_sample;
nuclear@26 217 blk.frame = ctx->cur_frame;
nuclear@26 218
nuclear@31 219 ctx->tpool->add_work(std::bind(render_block, ctx, blk));
nuclear@26 220
nuclear@26 221 x += BLKSZ;
nuclear@26 222 }
nuclear@26 223 y += BLKSZ;
nuclear@2 224 }
nuclear@2 225
nuclear@26 226 ++ctx->cur_sample;
nuclear@31 227 ctx->tpool->wait(timeout); // wait for completion
nuclear@26 228 return ctx->cur_sample > erb_getopti(ctx, ERB_OPT_MAX_SAMPLES) ? 0 : 1;
nuclear@26 229 }
nuclear@4 230
nuclear@2 231
nuclear@2 232 int erb_get_progress(struct erebus *ctx)
nuclear@2 233 {
nuclear@32 234 struct erb_render_status st;
nuclear@32 235 if(erb_get_status(ctx, &st) == -1) {
nuclear@32 236 return 0;
nuclear@32 237 }
nuclear@32 238 return st.progress_percent;
nuclear@32 239 }
nuclear@32 240
nuclear@32 241 int erb_get_status(struct erebus *ctx, struct erb_render_status *stat)
nuclear@32 242 {
nuclear@32 243 long pending = ctx->tpool->pending();
nuclear@34 244
nuclear@32 245 int xsz = ctx->fbimg.get_width();
nuclear@32 246 int ysz = ctx->fbimg.get_height();
nuclear@32 247 int xblocks = (xsz + BLKSZ - 1) / BLKSZ;
nuclear@32 248 int yblocks = (ysz + BLKSZ - 1) / BLKSZ;
nuclear@32 249 long num_blocks = xblocks * yblocks;
nuclear@32 250
nuclear@32 251 stat->frames = stat->max_frames = 0; // TODO
nuclear@32 252
nuclear@32 253 stat->blocks = num_blocks - pending;
nuclear@32 254 stat->max_blocks = num_blocks;
nuclear@32 255
nuclear@32 256 stat->samples = ctx->cur_sample ? ctx->cur_sample - 1 : 0;
nuclear@32 257 if((stat->max_samples = erb_getopti(ctx, ERB_OPT_MAX_SAMPLES)) == INF_SAMPLES) {
nuclear@32 258 stat->max_samples = stat->samples;
nuclear@32 259
nuclear@34 260 if(stat->max_blocks) {
nuclear@34 261 stat->progress_percent = 100 * stat->blocks / stat->max_blocks;
nuclear@34 262 } else {
nuclear@34 263 stat->progress_percent = 0;
nuclear@34 264 }
nuclear@32 265 } else {
nuclear@34 266 if(stat->max_samples) {
nuclear@34 267 stat->progress_percent = 100 * stat->samples / stat->max_samples;
nuclear@34 268 } else {
nuclear@34 269 stat->progress_percent = 0;
nuclear@34 270 }
nuclear@32 271 }
nuclear@32 272 return 0;
nuclear@2 273 }
nuclear@2 274
nuclear@2 275 int erb_load_scene(struct erebus *ctx, const char *fname)
nuclear@2 276 {
nuclear@37 277 if(!ctx->scn) {
nuclear@37 278 ctx->scn = new Scene;
nuclear@37 279 }
nuclear@2 280
nuclear@19 281 if(!ctx->scn->load(fname)) {
nuclear@19 282 return -1;
nuclear@19 283 }
nuclear@4 284 return 0;
nuclear@2 285 }
nuclear@2 286
nuclear@37 287 int erb_proc_cmd(struct erebus *ctx, const char *cmd)
nuclear@37 288 {
nuclear@37 289 if(!ctx->scn) {
nuclear@37 290 ctx->scn = new Scene;
nuclear@37 291 }
nuclear@37 292
nuclear@37 293 char *buf = (char*)alloca(strlen(cmd) + 1);
nuclear@37 294 strcpy(buf, cmd);
nuclear@37 295
nuclear@37 296 std::vector<char*> args;
nuclear@37 297 char *tok;
nuclear@37 298 while((tok = strtok(buf, " \t\v\n\r"))) {
nuclear@37 299 buf = 0;
nuclear@37 300 args.push_back(tok);
nuclear@37 301 }
nuclear@37 302 args.push_back(0);
nuclear@37 303
nuclear@37 304 if(!ctx->scn->proc_cmd(args.size() - 1, &args[0])) {
nuclear@37 305 return -1;
nuclear@37 306 }
nuclear@37 307 return 0;
nuclear@37 308 }
nuclear@37 309
nuclear@9 310 bool erb_input_keyboard(struct erebus *ctx, int key, bool pressed)
nuclear@9 311 {
nuclear@9 312 if(!ctx) return false;
nuclear@15 313 if((int)ctx->keystate.size() <= key) {
nuclear@15 314 ctx->keystate.resize(key < 256 ? 256 : key + 1);
nuclear@15 315 }
nuclear@9 316
nuclear@9 317 ctx->keystate[key] = pressed;
nuclear@10 318
nuclear@10 319 if(pressed) {
nuclear@10 320 switch(key) {
nuclear@15 321 case '.':
nuclear@15 322 {
nuclear@15 323 int node_count = ctx->scn->get_node_count();
nuclear@15 324 if(node_count && ++ctx->dbg_nodesel >= node_count) {
nuclear@15 325 ctx->dbg_nodesel = 0;
nuclear@15 326 }
nuclear@15 327 printf("selected node: %d\n", ctx->dbg_nodesel);
nuclear@15 328 }
nuclear@15 329 break;
nuclear@15 330
nuclear@15 331 case ',':
nuclear@15 332 {
nuclear@15 333 int node_count = ctx->scn->get_node_count();
nuclear@15 334 if(node_count && --ctx->dbg_nodesel < 0) {
nuclear@15 335 ctx->dbg_nodesel = node_count - 1;
nuclear@15 336 }
nuclear@15 337 printf("selected node: %d\n", ctx->dbg_nodesel);
nuclear@15 338 }
nuclear@15 339 break;
nuclear@15 340
nuclear@10 341 case '=':
nuclear@10 342 case '-':
nuclear@15 343 case '0':
nuclear@15 344 if(ctx->dbg_nodesel != -1) {
nuclear@15 345 SceneNode *node = ctx->scn->get_node(ctx->dbg_nodesel);
nuclear@15 346 Vector3 s = node->get_scaling();
nuclear@15 347 switch(key) {
nuclear@15 348 case '=':
nuclear@15 349 node->set_scaling(s * 1.1);
nuclear@15 350 break;
nuclear@15 351 case '-':
nuclear@15 352 node->set_scaling(s * 0.9);
nuclear@15 353 break;
nuclear@15 354 case '0':
nuclear@15 355 node->set_scaling(Vector3(1, 1, 1));
nuclear@15 356 break;
nuclear@15 357 }
nuclear@15 358 }
nuclear@15 359 erb_begin_frame(ctx, 0);
nuclear@15 360 return true;
nuclear@10 361 }
nuclear@10 362 }
nuclear@9 363 return false;
nuclear@9 364 }
nuclear@9 365
nuclear@9 366 bool erb_input_mouse_button(struct erebus *ctx, int bn, bool pressed, int x, int y)
nuclear@9 367 {
nuclear@9 368 if(!ctx) return false;
nuclear@15 369 if((int)ctx->bnstate.size() <= bn) {
nuclear@15 370 ctx->bnstate.resize(bn < 32 ? 32 : bn + 1);
nuclear@15 371 }
nuclear@9 372
nuclear@9 373 ctx->bnstate[bn] = pressed;
nuclear@9 374 ctx->mouse_pos[0] = x;
nuclear@9 375 ctx->mouse_pos[1] = y;
nuclear@10 376 return false;
nuclear@9 377 }
nuclear@9 378
nuclear@10 379 bool erb_input_mouse_motion(struct erebus *ctx, int x, int y)
nuclear@10 380 {
nuclear@18 381 bool res = false;
nuclear@18 382
nuclear@18 383 if(!ctx) return res;
nuclear@18 384
nuclear@18 385 int dx = x - ctx->mouse_pos[0];
nuclear@18 386 int dy = y - ctx->mouse_pos[1];
nuclear@18 387
nuclear@18 388 if(dx || dy) {
nuclear@18 389 TargetCamera *cam = (TargetCamera*)ctx->scn->get_active_camera();
nuclear@18 390 if(cam && ctx->bnstate[0]) {
nuclear@18 391 Vector3 cpos = cam->get_position();
nuclear@18 392 float mag = cpos.length();
nuclear@18 393
nuclear@19 394 float theta = atan2(cpos.z / mag, cpos.x / mag) - DEG_TO_RAD(dx * 0.5);
nuclear@19 395 float phi = acos(cpos.y / mag) - DEG_TO_RAD(dy * 0.5);
nuclear@18 396
nuclear@18 397 if(phi < 0) phi = 0;
nuclear@18 398 if(phi > M_PI) phi = M_PI;
nuclear@18 399
nuclear@18 400 cpos.x = cos(theta) * sin(phi) * mag;
nuclear@18 401 cpos.y = cos(phi) * mag;
nuclear@18 402 cpos.z = sin(theta) * sin(phi) * mag;
nuclear@18 403 cam->set_position(cpos);
nuclear@18 404
nuclear@18 405 erb_begin_frame(ctx, 0);
nuclear@18 406 res = true;
nuclear@18 407 }
nuclear@18 408 }
nuclear@10 409
nuclear@10 410 ctx->mouse_pos[0] = x;
nuclear@10 411 ctx->mouse_pos[1] = y;
nuclear@18 412 return res;
nuclear@10 413 }
nuclear@10 414
nuclear@10 415 bool erb_input_6dof_button(struct erebus *ctx, int bn, bool pressed)
nuclear@10 416 {
nuclear@10 417 if(!ctx) return false;
nuclear@10 418 return false;
nuclear@10 419 }
nuclear@10 420
nuclear@10 421 bool erb_input_6dof_motion(struct erebus *ctx, float x, float y, float z)
nuclear@10 422 {
nuclear@10 423 if(!ctx) return false;
nuclear@10 424 return false;
nuclear@10 425 }
nuclear@9 426
nuclear@9 427
nuclear@2 428 } // extern "C"
nuclear@2 429
nuclear@2 430 float randf(float low, float high)
nuclear@2 431 {
nuclear@2 432 std::uniform_real_distribution<float> unirnd(low, high);
nuclear@2 433 return unirnd(rnd_gen);
nuclear@2 434 }
nuclear@2 435
nuclear@26 436 static void render_block(struct erebus *ctx, Block blk)
nuclear@26 437 {
nuclear@26 438 if(blk.frame < ctx->cur_frame) {
nuclear@26 439 return; // skip stale blocks
nuclear@26 440 }
nuclear@26 441
nuclear@26 442 for(int i=0; i<blk.height; i++) {
nuclear@26 443 for(int j=0; j<blk.width; j++) {
nuclear@26 444 render_pixel(ctx, blk.x + j, blk.y + i, blk.sample);
nuclear@26 445 }
nuclear@26 446 }
nuclear@26 447 }
nuclear@26 448
nuclear@8 449 static void render_pixel(struct erebus *ctx, int x, int y, int sample)
nuclear@2 450 {
nuclear@5 451 Camera *cam = ctx->scn->get_active_camera();
nuclear@5 452 if(!cam) return;
nuclear@5 453
nuclear@5 454 int xsz = ctx->fbimg.get_width();
nuclear@5 455 int ysz = ctx->fbimg.get_height();
nuclear@8 456 int offs = (y * xsz + x) * 4;
nuclear@5 457
nuclear@8 458 float *pix = ctx->fbimg.get_pixels() + offs;
nuclear@8 459 float *accum = ctx->accum.get_pixels() + offs;
nuclear@8 460
nuclear@8 461 Ray ray = cam->get_primary_ray(x, y, xsz, ysz, sample);
nuclear@43 462 ray.energy = 1.0;
nuclear@43 463
nuclear@17 464 Color c = ray_trace(ctx, ray, 0);
nuclear@8 465 accum[0] += c.x;
nuclear@8 466 accum[1] += c.y;
nuclear@8 467 accum[2] += c.z;
nuclear@8 468 accum[3] += c.w;
nuclear@8 469
nuclear@8 470 float inv_samples = 1.0f / (float)(sample + 1);
nuclear@17 471 pix[0] = pow(accum[0] * inv_samples, ctx->inv_gamma);
nuclear@17 472 pix[1] = pow(accum[1] * inv_samples, ctx->inv_gamma);
nuclear@17 473 pix[2] = pow(accum[2] * inv_samples, ctx->inv_gamma);
nuclear@43 474 pix[3] = 1.0;//accum[3] * inv_samples;
nuclear@2 475 }