15 static void cleanup();
16 static void begin_frame(long tm);
17 static void end_frame();
18 static void resize_rtarget(int xsz, int ysz);
19 static void update_rect(int x, int y, int xsz, int ysz, float *pixels);
21 static void display();
22 static void display_statusbar(const struct erb_render_status *status);
23 static void save_image(const char *fname);
24 static void reshape(int x, int y);
25 static void keyb(unsigned char key, int x, int y);
26 static void keyb_up(unsigned char key, int x, int y);
27 static void skeyb(int key, int x, int y);
28 static void mouse(int bn, int st, int x, int y);
29 static void motion(int x, int y);
30 static int next_pow2(int x);
31 static void sighandler(int s);
32 static int parse_args(int argc, char **argv);
33 static void con_parse(const char *line, void *cls);
35 static int win_width, win_height, width, height, rtex_width, rtex_height;
36 static unsigned int rtex;
38 static int opt_samples = -1;
39 static int opt_iter = -1;
40 static int opt_threads = -1;
41 static float opt_imgscale = 2.0f;
43 static struct erebus *erb;
44 static int render_pending;
45 static int show_status = 1;
46 static unsigned long start_time;
48 static struct input_file {
50 struct input_file *next;
51 } *input_files, *input_files_tail;
54 static struct dtx_font *font;
56 int main(int argc, char **argv)
58 glutInitWindowSize(1024, 600);
59 glutInit(&argc, argv);
61 if(parse_args(argc, argv) == -1) {
65 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
66 glutCreateWindow("erebus OpenGL frontend");
68 glutDisplayFunc(display);
69 glutReshapeFunc(reshape);
70 glutKeyboardFunc(keyb);
71 glutKeyboardUpFunc(keyb_up);
72 glutSpecialFunc(skeyb);
74 glutMotionFunc(motion);
80 signal(SIGINT, sighandler);
81 signal(SIGSEGV, sighandler);
82 signal(SIGILL, sighandler);
83 signal(SIGTERM, sighandler);
84 signal(SIGFPE, sighandler);
92 struct input_file *infile;
94 width = glutGet(GLUT_WINDOW_WIDTH) / opt_imgscale;
95 height = glutGet(GLUT_WINDOW_HEIGHT) / opt_imgscale;
97 if(!(font = dtx_open_font_glyphmap("data/serif.glyphmap"))) {
98 fprintf(stderr, "warning: failed to load font!\n");
101 struct dtx_font *confont = dtx_open_font_glyphmap("data/mono.glyphmap");
103 con_set_font(confont, 14);
105 con_set_font(font, FONTSZ);
107 con_set_command_func(con_parse, 0);
109 if(!(erb = erb_init())) {
112 erb_setopti(erb, ERB_OPT_WIDTH, width);
113 erb_setopti(erb, ERB_OPT_HEIGHT, height);
115 if(opt_samples != -1) {
116 erb_setopti(erb, ERB_OPT_MAX_SAMPLES, opt_samples);
119 erb_setopti(erb, ERB_OPT_MAX_ITER, opt_iter);
121 if(opt_threads != -1) {
122 erb_setopti(erb, ERB_OPT_NUM_THREADS, opt_threads);
125 infile = input_files;
127 printf("loading scene file: %s\n", infile->fname);
128 if(erb_load_scene(erb, infile->fname) == -1) {
131 infile = infile->next;
138 glEnable(GL_TEXTURE_2D);
142 static void cleanup()
144 save_image("final.png");
148 static void begin_frame(long tm)
150 printf("rendering frame (t=%ld) ...\n", tm);
154 erb_begin_frame(erb, 0);
156 start_time = glutGet(GLUT_ELAPSED_TIME);
159 static void end_frame()
161 if(!render_pending) return;
163 long full_msec = glutGet(GLUT_ELAPSED_TIME) - start_time;
164 long msec, sec, min, hr, days;
167 con_printf("done in ");
168 if((sec = msec / 1000) > 0) {
170 if((min = sec / 60) > 0) {
172 if((hr = min / 60) > 0) {
174 if((days = hr / 24) > 0) {
176 con_printf("%ld days ", days);
178 con_printf("%ld hours ", hr);
180 con_printf("%ld min ", min);
182 con_printf("%ld sec ", sec);
184 con_printf("%ld ms (%ld total msec)\n", msec, full_msec);
190 static void resize_rtarget(int xsz, int ysz)
192 static unsigned char *defpix;
203 width = xsz / opt_imgscale;
204 height = ysz / opt_imgscale;
206 if(width > rtex_width || height > rtex_height) {
207 rtex_width = next_pow2(width);
208 rtex_height = next_pow2(height);
210 printf("resizing framebuffer texture: %dx%d\n", rtex_width, rtex_height);
213 glGenTextures(1, &rtex);
217 if(!(defpix = malloc(rtex_width * rtex_height * 4))) {
218 fprintf(stderr, "failed to allocate %dx%d framebuffer\n", rtex_width, rtex_height);
222 for(i=0; i<rtex_height; i++) {
223 for(j=0; j<rtex_width; j++) {
224 int chess = ((i >> 4) & 1) == ((j >> 4) & 1) ? 1 : 0;
225 int val = chess ? 64 : 48;
234 glBindTexture(GL_TEXTURE_2D, rtex);
235 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
236 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
237 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, rtex_width, rtex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, defpix);
245 static void update_rect(int x, int y, int xsz, int ysz, float *pixels)
247 glBindTexture(GL_TEXTURE_2D, rtex);
248 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, xsz, ysz, GL_RGBA, GL_FLOAT, pixels);
256 static void display()
258 static struct erb_render_status status;
262 if(erb_render(erb, 64) == 0) {
265 update_rect(0, 0, width, height, erb_get_framebuffer(erb));
266 erb_get_status(erb, &status);
269 maxu = (float)width / (float)rtex_width;
270 maxv = (float)height / (float)rtex_height;
272 glEnable(GL_TEXTURE_2D);
273 glBindTexture(GL_TEXTURE_2D, rtex);
276 glColor4f(1, 1, 1, 1);
277 glTexCoord2f(0, maxv); glVertex2f(-1, -1);
278 glTexCoord2f(maxu, maxv); glVertex2f(1, -1);
279 glTexCoord2f(maxu, 0); glVertex2f(1, 1);
280 glTexCoord2f(0, 0); glVertex2f(-1, 1);
287 // draw progress information etc...
289 display_statusbar(&status);
293 assert(glGetError() == GL_NO_ERROR);
296 static void display_statusbar(const struct erb_render_status *status)
298 int show_progress = opt_samples > 0;
299 int bar_height, prog_width;
302 dtx_use_font(font, FONTSZ);
304 glDisable(GL_TEXTURE_2D);
306 glMatrixMode(GL_PROJECTION);
309 glOrtho(0, win_width, 0, win_height, -1, 1);
311 glMatrixMode(GL_MODELVIEW);
316 dtx_glyph_box('Q', &bbox);
318 // draw progress/status bar
319 bar_height = bbox.height + 4;
320 prog_width = show_progress ? status->progress_percent * win_width / 100 : 0;
323 glColor4f(0, 0, 0, 1);
324 glVertex2f(prog_width, 0);
325 glVertex2f(win_width, 0);
326 glVertex2f(win_width, bar_height);
327 glVertex2f(prog_width, bar_height);
329 glColor4f(0.25, 0, 0, 1);
331 glVertex2f(prog_width, 0);
332 glVertex2f(prog_width, bar_height);
333 glVertex2f(0, bar_height);
337 glTranslatef(bbox.x + 2, bbox.y + 2, 0);
339 glColor4f(1, 1, 1, 1);
341 if(opt_samples > 0) {
342 dtx_printf("samples: %ld / %ld", status->samples, status->max_samples);
345 glTranslatef(win_width - dtx_string_width("progress: 100%") - 2, bbox.y + 2, 0);
346 dtx_printf("progress: %ld%%", status->progress_percent);
348 dtx_printf("samples: %ld", status->samples);
352 // samples/sec display
353 static unsigned long paths_per_sec, prev_msec, prev_paths;
355 unsigned long msec = glutGet(GLUT_ELAPSED_TIME) - start_time;
356 unsigned long dt = msec - prev_msec;
358 if(dt >= 1500) { // average over 1.5 seconds
359 unsigned long paths = status->samples * width * height;
360 if(prev_msec > 0 && prev_paths <= paths) { // check valid interval (not a restart or whatever)
361 paths_per_sec = 1000 * (paths - prev_paths) / dt;
368 glTranslatef((win_width - dtx_string_width("paths/s: 999999")) / 2, bbox.y + 2, 0);
370 dtx_printf("paths/s: %ld", paths_per_sec);
372 dtx_printf("paths/s: ???");
377 glMatrixMode(GL_PROJECTION);
381 static void save_image(const char *fname)
383 float *fb = erb_get_framebuffer(erb);
385 if(img_save_pixels(fname ? fname : "output.png", fb, width, height, IMG_FMT_RGBAF) == -1) {
386 fprintf(stderr, "failed to save image\n");
390 static void reshape(int x, int y)
392 glViewport(0, 0, x, y);
393 resize_rtarget(x, y);
395 erb_setopti(erb, ERB_OPT_WIDTH, width);
396 erb_setopti(erb, ERB_OPT_HEIGHT, height);
399 static void keyb(unsigned char key, int x, int y)
403 if(con_is_visible()) {
413 if(!con_is_visible()) {
422 con_set_visible(!con_is_visible());
427 show_status = !show_status;
432 // otherwise if the console is visible, let them through
433 if(con_is_visible()) {
436 return; // don't pass anything to the erb input handler
440 /* TODO handle input */
443 static void keyb_up(unsigned char key, int x, int y)
445 /* TODO handle input */
448 static void skeyb(int key, int x, int y)
450 if(key == GLUT_KEY_F12) {
451 printf("saving image...\n");
456 if(con_is_visible()) {
463 con_input_key(CON_KEY_LEFT);
466 con_input_key(CON_KEY_RIGHT);
469 con_input_key(CON_KEY_UP);
472 con_input_key(CON_KEY_DOWN);
475 con_input_key(CON_KEY_HOME);
478 con_input_key(CON_KEY_END);
480 case GLUT_KEY_INSERT:
481 con_input_key(CON_KEY_INS);
483 case GLUT_KEY_PAGE_UP:
484 con_input_key(CON_KEY_PGUP);
486 case GLUT_KEY_PAGE_DOWN:
487 con_input_key(CON_KEY_PGDOWN);
497 static void mouse(int bn, int st, int x, int y)
499 /* TODO handle input */
502 static void motion(int x, int y)
504 /* TODO handle input */
507 static int next_pow2(int x)
516 static void sighandler(int s)
521 static int parse_args(int argc, char **argv)
525 for(i=1; i<argc; i++) {
526 if(argv[i][0] == '-') {
527 if(strcmp(argv[i], "-samples") == 0) {
528 opt_samples = atoi(argv[++i]);
529 if(opt_samples <= 0) {
530 fprintf(stderr, "invalid -samples option: %s\n", argv[i]);
534 } else if(strcmp(argv[i], "-iter") == 0) {
535 opt_iter = atoi(argv[++i]);
537 fprintf(stderr, "invalid -iter option: %s\n", argv[i]);
541 } else if(strcmp(argv[i], "-threads") == 0) {
542 opt_threads = atoi(argv[++i]);
543 if(opt_threads <= 0) {
544 fprintf(stderr, "invalid -threads option: %s\n", argv[i]);
548 } else if(strcmp(argv[i], "-scale") == 0) {
549 opt_imgscale = atof(argv[++i]);
550 if(opt_imgscale <= 0.0f) {
551 fprintf(stderr, "invalid -scale option: %s\n", argv[i]);
556 fprintf(stderr, "invalid option: %s\n", argv[i]);
560 struct input_file *infile;
562 if(!(infile = malloc(sizeof *infile))) {
563 fprintf(stderr, "failed to allocate input file node\n");
566 infile->fname = argv[i];
569 if(input_files_tail) {
570 input_files_tail->next = infile;
571 input_files_tail = infile;
573 input_files = input_files_tail = infile;
581 static void con_parse(const char *line, void *cls)
588 if(!(len = strlen(line))) return;
590 buf = alloca(len + 1);
591 memcpy(buf, line, len + 1);
593 while((tok = strtok(buf, " \n\r\v\t"))) {
601 if(strcmp(args[0], "exit") == 0 || strcmp(args[0], "quit") == 0) {
604 } else if(strcmp(args[0], "clear") == 0) {
607 } else if(strcmp(args[0], "stop") == 0) {
610 } else if(strcmp(args[0], "render") == 0) {
614 tm = strtol(args[1], &endp, 10);
615 if(endp == args[1]) {
616 con_printf("the argument to render must be a time value in milliseconds\n");
622 } else if(strcmp(args[0], "samples") == 0) {
623 if(args[1] && (opt_samples = atoi(args[1]))) {
624 erb_setopti(erb, ERB_OPT_MAX_SAMPLES, opt_samples);
625 con_printf("max samples is now %d\n", opt_samples);
627 con_printf("invalid samples command: %s\n", line);
630 } else if(strcmp(args[0], "load") == 0) {
631 for(i=1; i<argc; i++) {
632 if(erb_load_scene(erb, args[i]) == -1) {
633 con_printf("failed to load scene: %s\n", args[1]);
640 } else if(strcmp(args[0], "add") == 0) {
641 if(erb_proc_cmd(erb, line + 4) == -1) {
642 con_puts("invalid add command\n");
648 } else if(strcmp(args[0], "help") == 0) {
649 con_printf("Available commands:\n");
650 con_printf("render [anim time] begins rendering a frame with the given time (default: 0)\n");
651 con_printf("stop stops rendering\n");
652 con_printf("samples <count> sets the maximum number of samples per pixel\n");
653 con_printf("iter <count> sets the maximum number of ray bounces\n");
654 con_printf("load <filename> loads a scene file\n");
655 con_printf("add <object command> adds another object to the scene. For valid object commands see test/scene\n");
658 con_printf("unrecognized command: %s\n", args[0]);