erebus

view src/main.cc @ 40:9d6368850fe1

minor enhancements and bugfixes to the console stuff
author John Tsiombikas <nuclear@member.fsf.org>
date Tue, 10 Jun 2014 10:53:19 +0300
parents fd45cf0fc912
children 2e817711d0f6
line source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <assert.h>
5 #include <signal.h>
6 #include <vector>
7 #include <chrono>
8 #include <imago2.h>
9 #include <drawtext.h>
10 #include "opengl.h"
11 #include "erebus.h"
12 #include "console.h"
14 using namespace std::chrono;
16 static bool init();
17 static void cleanup();
18 static void begin_frame(long tm);
19 static void end_frame();
20 static void resize_rtarget(int xsz, int ysz);
21 static void update_rect(int x, int y, int xsz, int ysz, float *pixels);
22 static void idle();
23 static void display();
24 static void display_statusbar(const erb_render_status &status);
25 static void save_image(const char *fname = 0);
26 static void reshape(int x, int y);
27 static void keyb(unsigned char key, int x, int y);
28 static void keyb_up(unsigned char key, int x, int y);
29 static void skeyb(int key, int x, int y);
30 static void mouse(int bn, int st, int x, int y);
31 static void motion(int x, int y);
32 static void sball_button(int bn, int st);
33 static void sball_motion(int x, int y, int z);
34 static int next_pow2(int x);
35 static void sighandler(int s);
36 static bool parse_args(int argc, char **argv);
37 static void con_parse(const char *line);
39 static int win_width, win_height, width, height, rtex_width, rtex_height;
40 static unsigned int rtex;
42 static int opt_samples = -1;
43 static int opt_iter = -1;
44 static int opt_threads = -1;
45 static float opt_imgscale = 2.0f;
47 static erebus *erb;
48 static bool render_pending;
49 static bool show_status = true;
50 static steady_clock::time_point start_time;
52 static std::vector<char*> sfiles;
54 #define FONTSZ 22
55 static dtx_font *font;
56 static Console con;
58 int main(int argc, char **argv)
59 {
60 glutInitWindowSize(1024, 600);
61 glutInit(&argc, argv);
63 if(!parse_args(argc, argv)) {
64 return 1;
65 }
67 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
68 glutCreateWindow("erebus OpenGL frontend");
70 glutDisplayFunc(display);
71 glutReshapeFunc(reshape);
72 glutKeyboardFunc(keyb);
73 glutKeyboardUpFunc(keyb_up);
74 glutSpecialFunc(skeyb);
75 glutMouseFunc(mouse);
76 glutMotionFunc(motion);
77 glutSpaceballButtonFunc(sball_button);
78 glutSpaceballMotionFunc(sball_motion);
80 if(!init()) {
81 return 1;
82 }
83 atexit(cleanup);
84 signal(SIGINT, sighandler);
85 signal(SIGSEGV, sighandler);
86 signal(SIGILL, sighandler);
87 signal(SIGTERM, sighandler);
88 signal(SIGFPE, sighandler);
90 glutMainLoop();
91 }
93 static bool init()
94 {
95 width = glutGet(GLUT_WINDOW_WIDTH) / opt_imgscale;
96 height = glutGet(GLUT_WINDOW_HEIGHT) / opt_imgscale;
98 //if(!(font = dtx_open_font("/usr/share/fonts/opentype/linux-libertine/LinLibertine_R.otf", FONTSZ))) {
99 if(!(font = dtx_open_font_glyphmap("data/serif.glyphmap"))) {
100 fprintf(stderr, "warning: failed to load font!\n");
101 }
103 //dtx_font *confont = dtx_open_font("/usr/share/fonts/truetype/droid/DroidSansMono.ttf", 14);
104 dtx_font *confont = dtx_open_font_glyphmap("data/mono.glyphmap");
105 if(confont) {
106 con.set_font(confont, 14);
107 } else {
108 con.set_font(font, FONTSZ);
109 }
110 con.set_command_func(con_parse);
112 if(!(erb = erb_init())) {
113 return false;
114 }
115 erb_setopti(erb, ERB_OPT_WIDTH, width);
116 erb_setopti(erb, ERB_OPT_HEIGHT, height);
118 if(opt_samples != -1) {
119 erb_setopti(erb, ERB_OPT_MAX_SAMPLES, opt_samples);
120 }
121 if(opt_iter != -1) {
122 erb_setopti(erb, ERB_OPT_MAX_ITER, opt_iter);
123 }
124 if(opt_threads != -1) {
125 erb_setopti(erb, ERB_OPT_NUM_THREADS, opt_threads);
126 }
128 for(size_t i=0; i<sfiles.size(); i++) {
129 printf("loading scene file: %s\n", sfiles[i]);
130 if(erb_load_scene(erb, sfiles[i]) == -1) {
131 return false;
132 }
133 }
135 if(!sfiles.empty()) {
136 begin_frame(0);
137 }
139 glEnable(GL_TEXTURE_2D);
140 return true;
141 }
143 static void cleanup()
144 {
145 save_image("final.png");
146 erb_destroy(erb);
147 }
149 static void begin_frame(long tm)
150 {
151 printf("rendering frame (t=%ld) ... ", tm);
152 fflush(stdout);
154 render_pending = true;
155 glutIdleFunc(idle);
156 erb_begin_frame(erb, 0);
158 start_time = steady_clock::now();
159 }
161 static void end_frame()
162 {
163 if(!render_pending) return;
165 auto dur = steady_clock::now() - start_time;
166 long full_msec = duration_cast<milliseconds>(dur).count();
167 long msec, sec, min, hr, days;
169 msec = full_msec;
170 printf("done in ");
171 if((sec = msec / 1000) > 0) {
172 msec %= 1000;
173 if((min = sec / 60) > 0) {
174 sec %= 60;
175 if((hr = min / 60) > 0) {
176 min %= 60;
177 if((days = hr / 24) > 0) {
178 hr %= 24;
179 printf("%ld days ", days);
180 }
181 printf("%ld hours ", hr);
182 }
183 printf("%ld min ", min);
184 }
185 printf("%ld sec ", sec);
186 }
187 printf("%ld ms (%ld total msec)\n", msec, full_msec);
189 render_pending = false;
190 glutIdleFunc(0);
191 }
193 static void resize_rtarget(int xsz, int ysz)
194 {
195 static unsigned char *defpix;
197 win_width = xsz;
198 win_height = ysz;
200 width = xsz / opt_imgscale;
201 height = ysz / opt_imgscale;
203 if(width <= rtex_width && height <= rtex_height) {
204 return;
205 }
206 rtex_width = next_pow2(width);
207 rtex_height = next_pow2(height);
209 printf("resizing framebuffer texture: %dx%d\n", rtex_width, rtex_height);
211 if(!rtex) {
212 glGenTextures(1, &rtex);
213 }
215 delete [] defpix;
216 defpix = new unsigned char[rtex_width * rtex_height * 4];
217 unsigned char *ptr = defpix;
218 for(int i=0; i<rtex_height; i++) {
219 for(int j=0; j<rtex_width; j++) {
220 bool chess = ((i >> 4) & 1) == ((j >> 4) & 1);
222 int val = chess ? 64 : 48;
224 *ptr++ = val;
225 *ptr++ = val;
226 *ptr++ = val;
227 *ptr++ = 255;
228 }
229 }
231 glBindTexture(GL_TEXTURE_2D, rtex);
232 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
233 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
234 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, rtex_width, rtex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, defpix);
235 }
237 static void update_rect(int x, int y, int xsz, int ysz, float *pixels)
238 {
239 glBindTexture(GL_TEXTURE_2D, rtex);
240 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, xsz, ysz, GL_RGBA, GL_FLOAT, pixels);
241 }
243 static void idle()
244 {
245 glutPostRedisplay();
246 }
248 static void display()
249 {
250 static struct erb_render_status status;
252 if(render_pending) {
253 if(erb_render(erb, 64) == 0) {
254 end_frame();
255 }
256 update_rect(0, 0, width, height, erb_get_framebuffer(erb));
257 erb_get_status(erb, &status);
258 }
260 float maxu = (float)width / (float)rtex_width;
261 float maxv = (float)height / (float)rtex_height;
263 glEnable(GL_TEXTURE_2D);
264 glBindTexture(GL_TEXTURE_2D, rtex);
266 glBegin(GL_QUADS);
267 glColor4f(1, 1, 1, 1);
268 glTexCoord2f(0, maxv); glVertex2f(-1, -1);
269 glTexCoord2f(maxu, maxv); glVertex2f(1, -1);
270 glTexCoord2f(maxu, 0); glVertex2f(1, 1);
271 glTexCoord2f(0, 0); glVertex2f(-1, 1);
272 glEnd();
274 // draw the console
275 con.update();
276 con.draw();
278 // draw progress information etc...
279 if(show_status) {
280 display_statusbar(status);
281 }
283 glutSwapBuffers();
284 assert(glGetError() == GL_NO_ERROR);
285 }
287 static void display_statusbar(const erb_render_status &status)
288 {
289 if(!font) return;
290 dtx_use_font(font, FONTSZ);
292 bool show_progress = opt_samples > 0;
294 glDisable(GL_TEXTURE_2D);
296 glMatrixMode(GL_PROJECTION);
297 glPushMatrix();
298 glLoadIdentity();
299 glOrtho(0, win_width, 0, win_height, -1, 1);
301 glMatrixMode(GL_MODELVIEW);
302 glPushMatrix();
303 glLoadIdentity();
305 dtx_box bbox;
306 dtx_glyph_box('Q', &bbox);
308 // draw progress/status bar
309 int bar_height = bbox.height + 4;
310 int prog_width = show_progress ? status.progress_percent * win_width / 100 : 0;
312 glBegin(GL_QUADS);
313 glColor4f(0, 0, 0, 1);
314 glVertex2f(prog_width, 0);
315 glVertex2f(win_width, 0);
316 glVertex2f(win_width, bar_height);
317 glVertex2f(prog_width, bar_height);
319 glColor4f(0.25, 0, 0, 1);
320 glVertex2f(0, 0);
321 glVertex2f(prog_width, 0);
322 glVertex2f(prog_width, bar_height);
323 glVertex2f(0, bar_height);
324 glEnd();
326 // draw the text
327 glTranslatef(bbox.x + 2, bbox.y + 2, 0);
329 glColor4f(1, 1, 1, 1);
331 if(opt_samples > 0) {
332 dtx_printf("samples: %ld / %ld", status.samples, status.max_samples);
334 glLoadIdentity();
335 glTranslatef(win_width - dtx_string_width("progress: 100%") - 2, bbox.y + 2, 0);
336 dtx_printf("progress: %ld%%", status.progress_percent);
337 } else {
338 dtx_printf("samples: %ld", status.samples);
339 }
341 // samples/sec display
342 static long paths_per_sec, prev_msec, prev_paths;
344 long msec = duration_cast<milliseconds>(steady_clock::now() - start_time).count();
345 long dt = msec - prev_msec;
347 if(dt >= 1500) { // average over 1.5 seconds
348 long paths = status.samples * width * height;
349 if(prev_msec > 0 && prev_paths <= paths) { // check valid interval (not a restart or whatever)
350 paths_per_sec = 1000 * (paths - prev_paths) / dt;
351 }
352 prev_msec = msec;
353 prev_paths = paths;
354 }
356 glLoadIdentity();
357 glTranslatef((win_width - dtx_string_width("paths/s: 999999")) / 2, bbox.y + 2, 0);
358 if(paths_per_sec) {
359 dtx_printf("paths/s: %ld", paths_per_sec);
360 } else {
361 dtx_printf("paths/s: ???");
362 }
364 glPopMatrix();
365 glMatrixMode(GL_PROJECTION);
366 glPopMatrix();
367 }
369 static void save_image(const char *fname)
370 {
371 float *fb = erb_get_framebuffer(erb);
373 if(img_save_pixels(fname ? fname : "output.png", fb, width, height, IMG_FMT_RGBAF) == -1) {
374 fprintf(stderr, "failed to save image\n");
375 }
376 }
378 static void reshape(int x, int y)
379 {
380 glViewport(0, 0, x, y);
381 resize_rtarget(x, y);
383 erb_setopti(erb, ERB_OPT_WIDTH, width);
384 erb_setopti(erb, ERB_OPT_HEIGHT, height);
385 }
387 static void keyb(unsigned char key, int x, int y)
388 {
389 switch(key) {
390 case 27:
391 if(con.is_visible()) {
392 con.hide();
393 glutPostRedisplay();
394 } else {
395 end_frame();
396 exit(0);
397 }
398 break;
400 case ' ':
401 if(!con.is_visible()) {
402 begin_frame(0);
403 } else {
404 con.input_key(' ');
405 glutPostRedisplay();
406 }
407 break;
409 case '`':
410 con.set_visible(!con.is_visible());
411 glutPostRedisplay();
412 break;
414 case '~':
415 show_status = !show_status;
416 glutPostRedisplay();
417 break;
419 default:
420 // otherwise if the console is visible, let them through
421 if(con.is_visible()) {
422 con.input_key(key);
423 glutPostRedisplay();
424 return; // don't pass anything to the erb input handler
425 }
426 }
428 if(erb_input_keyboard(erb, key, true)) {
429 glutPostRedisplay();
430 }
431 }
433 static void keyb_up(unsigned char key, int x, int y)
434 {
435 if(erb_input_keyboard(erb, key, false)) {
436 glutPostRedisplay();
437 }
438 }
440 static void skeyb(int key, int x, int y)
441 {
442 if(key == GLUT_KEY_F12) {
443 printf("saving image...\n");
444 save_image();
445 return;
446 }
448 if(con.is_visible()) {
449 switch(key) {
450 case GLUT_KEY_F8:
451 con.debug();
452 return;
454 case GLUT_KEY_LEFT:
455 con.input_key(Console::KEY_LEFT);
456 break;
457 case GLUT_KEY_RIGHT:
458 con.input_key(Console::KEY_RIGHT);
459 break;
460 case GLUT_KEY_UP:
461 con.input_key(Console::KEY_UP);
462 break;
463 case GLUT_KEY_DOWN:
464 con.input_key(Console::KEY_DOWN);
465 break;
466 case GLUT_KEY_HOME:
467 con.input_key(Console::KEY_HOME);
468 break;
469 case GLUT_KEY_END:
470 con.input_key(Console::KEY_END);
471 break;
472 case GLUT_KEY_INSERT:
473 con.input_key(Console::KEY_INS);
474 break;
475 case GLUT_KEY_PAGE_UP:
476 con.input_key(Console::KEY_PGUP);
477 break;
478 case GLUT_KEY_PAGE_DOWN:
479 con.input_key(Console::KEY_PGDOWN);
480 break;
482 default:
483 return;
484 }
485 glutPostRedisplay();
486 }
487 }
489 static void mouse(int bn, int st, int x, int y)
490 {
491 if(erb_input_mouse_button(erb, bn - GLUT_LEFT_BUTTON, st == GLUT_DOWN, x, y)) {
492 glutPostRedisplay();
493 }
494 }
496 static void motion(int x, int y)
497 {
498 if(erb_input_mouse_motion(erb, x, y)) {
499 glutPostRedisplay();
500 }
501 }
503 static void sball_button(int bn, int state)
504 {
505 if(erb_input_6dof_button(erb, bn, state == GLUT_DOWN)) {
506 glutPostRedisplay();
507 }
508 }
510 static void sball_motion(int x, int y, int z)
511 {
512 if(erb_input_6dof_motion(erb, x / 65536.0, y / 65536.0, z / 65536.0)) {
513 glutPostRedisplay();
514 }
515 }
517 static int next_pow2(int x)
518 {
519 int res = 2;
520 while(res < x) {
521 res <<= 1;
522 }
523 return res;
524 }
526 static void sighandler(int s)
527 {
528 exit(0);
529 }
531 static bool parse_args(int argc, char **argv)
532 {
533 for(int i=1; i<argc; i++) {
534 if(argv[i][0] == '-') {
535 if(strcmp(argv[i], "-samples") == 0) {
536 opt_samples = atoi(argv[++i]);
537 if(opt_samples <= 0) {
538 fprintf(stderr, "invalid -samples option: %s\n", argv[i]);
539 return false;
540 }
542 } else if(strcmp(argv[i], "-iter") == 0) {
543 opt_iter = atoi(argv[++i]);
544 if(opt_iter <= 0) {
545 fprintf(stderr, "invalid -iter option: %s\n", argv[i]);
546 return false;
547 }
549 } else if(strcmp(argv[i], "-threads") == 0) {
550 opt_threads = atoi(argv[++i]);
551 if(opt_threads <= 0) {
552 fprintf(stderr, "invalid -threads option: %s\n", argv[i]);
553 return false;
554 }
556 } else if(strcmp(argv[i], "-scale") == 0) {
557 opt_imgscale = atof(argv[++i]);
558 if(opt_imgscale <= 0.0f) {
559 fprintf(stderr, "invalid -scale option: %s\n", argv[i]);
560 return false;
561 }
563 } else {
564 fprintf(stderr, "invalid option: %s\n", argv[i]);
565 return false;
566 }
567 } else {
568 sfiles.push_back(argv[i]);
569 }
570 }
572 return true;
573 }
575 static void con_parse(const char *line)
576 {
577 int len = strlen(line);
578 if(!len) return;
580 char *buf = (char*)alloca(len + 1);
581 memcpy(buf, line, len + 1);
583 std::vector<char*> args;
584 char *tok;
586 while((tok = strtok(buf, " \n\r\v\t"))) {
587 buf = 0;
588 args.push_back(tok);
589 }
590 args.push_back(0);
591 int argc = args.size() - 1;
593 if(!args[0]) return;
595 if(strcmp(args[0], "exit") == 0 || strcmp(args[0], "quit") == 0) {
596 exit(0);
598 } else if(strcmp(args[0], "clear") == 0) {
599 erb_clear(erb);
601 } else if(strcmp(args[0], "stop") == 0) {
602 erb_end_frame(erb);
604 } else if(strcmp(args[0], "render") == 0) {
605 long tm = 0;
606 if(args[1]) {
607 char *endp;
608 tm = strtol(args[1], &endp, 10);
609 if(endp == args[1]) {
610 con.printf("the argument to render must be a time value in milliseconds\n");
611 return;
612 }
613 }
614 begin_frame(tm);
616 } else if(strcmp(args[0], "load") == 0) {
617 for(int i=1; i<argc; i++) {
618 if(erb_load_scene(erb, args[i]) == -1) {
619 con.printf("failed to load scene: %s\n", args[1]);
620 } else {
621 begin_frame(0);
622 }
623 }
625 } else if(strcmp(args[0], "add") == 0) {
626 if(erb_proc_cmd(erb, line + 4) == -1) {
627 con.puts("invalid add command\n");
628 } else {
629 begin_frame(0);
630 }
632 } else {
633 con.printf("unrecognized command: %s\n", args[0]);
634 }
635 }