erebus

annotate liberebus/src/erebus.cc @ 34:d15ee526daa6

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