initial commit
[erebus] / erebus / src / main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <assert.h>
5 #include <signal.h>
6 #include <alloca.h>
7 #include <imago2.h>
8 #include <drawtext.h>
9 #include "opengl.h"
10 #include <GL/glut.h>
11 #include "erebus.h"
12 #include "console.h"
13
14 static int init();
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);
20 static void idle();
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);
34
35 static int win_width, win_height, width, height, rtex_width, rtex_height;
36 static unsigned int rtex;
37
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;
42
43 static struct erebus *erb;
44 static int render_pending;
45 static int show_status = 1;
46 static unsigned long start_time;
47
48 static struct input_file {
49         char *fname;
50         struct input_file *next;
51 } *input_files, *input_files_tail;
52
53 #define FONTSZ  22
54 static struct dtx_font *font;
55
56 int main(int argc, char **argv)
57 {
58         glutInitWindowSize(1024, 600);
59         glutInit(&argc, argv);
60
61         if(parse_args(argc, argv) == -1) {
62                 return 1;
63         }
64
65         glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
66         glutCreateWindow("erebus OpenGL frontend");
67
68         glutDisplayFunc(display);
69         glutReshapeFunc(reshape);
70         glutKeyboardFunc(keyb);
71         glutKeyboardUpFunc(keyb_up);
72         glutSpecialFunc(skeyb);
73         glutMouseFunc(mouse);
74         glutMotionFunc(motion);
75
76         if(init() == -1) {
77                 return 1;
78         }
79         atexit(cleanup);
80         signal(SIGINT, sighandler);
81         signal(SIGSEGV, sighandler);
82         signal(SIGILL, sighandler);
83         signal(SIGTERM, sighandler);
84         signal(SIGFPE, sighandler);
85
86         glutMainLoop();
87         return 0;
88 }
89
90 static int init()
91 {
92         struct input_file *infile;
93
94         width = glutGet(GLUT_WINDOW_WIDTH) / opt_imgscale;
95         height = glutGet(GLUT_WINDOW_HEIGHT) / opt_imgscale;
96
97         if(!(font = dtx_open_font_glyphmap("data/serif.glyphmap"))) {
98                 fprintf(stderr, "warning: failed to load font!\n");
99         }
100
101         struct dtx_font *confont = dtx_open_font_glyphmap("data/mono.glyphmap");
102         if(confont) {
103                 con_set_font(confont, 14);
104         } else {
105                 con_set_font(font, FONTSZ);
106         }
107         con_set_command_func(con_parse, 0);
108
109         if(!(erb = erb_init())) {
110                 return -1;
111         }
112         erb_setopti(erb, ERB_OPT_WIDTH, width);
113         erb_setopti(erb, ERB_OPT_HEIGHT, height);
114
115         if(opt_samples != -1) {
116                 erb_setopti(erb, ERB_OPT_MAX_SAMPLES, opt_samples);
117         }
118         if(opt_iter != -1) {
119                 erb_setopti(erb, ERB_OPT_MAX_ITER, opt_iter);
120         }
121         if(opt_threads != -1) {
122                 erb_setopti(erb, ERB_OPT_NUM_THREADS, opt_threads);
123         }
124
125         infile = input_files;
126         while(infile) {
127                 printf("loading scene file: %s\n", infile->fname);
128                 if(erb_load_scene(erb, infile->fname) == -1) {
129                         return -1;
130                 }
131                 infile = infile->next;
132         }
133
134         if(input_files) {
135                 begin_frame(0);
136         }
137
138         glEnable(GL_TEXTURE_2D);
139         return 0;
140 }
141
142 static void cleanup()
143 {
144         save_image("final.png");
145         erb_destroy(erb);
146 }
147
148 static void begin_frame(long tm)
149 {
150         printf("rendering frame (t=%ld) ...\n", tm);
151
152         render_pending = 1;
153         glutIdleFunc(idle);
154         erb_begin_frame(erb, 0);
155
156         start_time = glutGet(GLUT_ELAPSED_TIME);
157 }
158
159 static void end_frame()
160 {
161         if(!render_pending) return;
162
163         long full_msec = glutGet(GLUT_ELAPSED_TIME) - start_time;
164         long msec, sec, min, hr, days;
165
166         msec = full_msec;
167         con_printf("done in ");
168         if((sec = msec / 1000) > 0) {
169                 msec %= 1000;
170                 if((min = sec / 60) > 0) {
171                         sec %= 60;
172                         if((hr = min / 60) > 0) {
173                                 min %= 60;
174                                 if((days = hr / 24) > 0) {
175                                         hr %= 24;
176                                         con_printf("%ld days ", days);
177                                 }
178                                 con_printf("%ld hours ", hr);
179                         }
180                         con_printf("%ld min ", min);
181                 }
182                 con_printf("%ld sec ", sec);
183         }
184         con_printf("%ld ms (%ld total msec)\n", msec, full_msec);
185
186         render_pending = 0;
187         glutIdleFunc(0);
188 }
189
190 static void resize_rtarget(int xsz, int ysz)
191 {
192         static unsigned char *defpix;
193         int i, j;
194         unsigned char *ptr;
195
196         if(render_pending) {
197                 erb_end_frame(erb);
198         }
199
200         win_width = xsz;
201         win_height = ysz;
202
203         width = xsz / opt_imgscale;
204         height = ysz / opt_imgscale;
205
206         if(width > rtex_width || height > rtex_height) {
207                 rtex_width = next_pow2(width);
208                 rtex_height = next_pow2(height);
209
210                 printf("resizing framebuffer texture: %dx%d\n", rtex_width, rtex_height);
211
212                 if(!rtex) {
213                         glGenTextures(1, &rtex);
214                 }
215
216                 free(defpix);
217                 if(!(defpix = malloc(rtex_width * rtex_height * 4))) {
218                         fprintf(stderr, "failed to allocate %dx%d framebuffer\n", rtex_width, rtex_height);
219                         abort();
220                 }
221                 ptr = defpix;
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;
226
227                                 *ptr++ = val;
228                                 *ptr++ = val;
229                                 *ptr++ = val;
230                                 *ptr++ = 255;
231                         }
232                 }
233
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);
238         }
239
240         if(render_pending) {
241                 begin_frame(0);
242         }
243 }
244
245 static void update_rect(int x, int y, int xsz, int ysz, float *pixels)
246 {
247         glBindTexture(GL_TEXTURE_2D, rtex);
248         glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, xsz, ysz, GL_RGBA, GL_FLOAT, pixels);
249 }
250
251 static void idle()
252 {
253         glutPostRedisplay();
254 }
255
256 static void display()
257 {
258         static struct erb_render_status status;
259         float maxu, maxv;
260
261         if(render_pending) {
262                 if(erb_render(erb, 64) == 0) {
263                         end_frame();
264                 }
265                 update_rect(0, 0, width, height, erb_get_framebuffer(erb));
266                 erb_get_status(erb, &status);
267         }
268
269         maxu = (float)width / (float)rtex_width;
270         maxv = (float)height / (float)rtex_height;
271
272         glEnable(GL_TEXTURE_2D);
273         glBindTexture(GL_TEXTURE_2D, rtex);
274
275         glBegin(GL_QUADS);
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);
281         glEnd();
282
283         // draw the console
284         con_update();
285         con_draw();
286
287         // draw progress information etc...
288         if(show_status) {
289                 display_statusbar(&status);
290         }
291
292         glutSwapBuffers();
293         assert(glGetError() == GL_NO_ERROR);
294 }
295
296 static void display_statusbar(const struct erb_render_status *status)
297 {
298         int show_progress = opt_samples > 0;
299         int bar_height, prog_width;
300
301         if(!font) return;
302         dtx_use_font(font, FONTSZ);
303
304         glDisable(GL_TEXTURE_2D);
305
306         glMatrixMode(GL_PROJECTION);
307         glPushMatrix();
308         glLoadIdentity();
309         glOrtho(0, win_width, 0, win_height, -1, 1);
310
311         glMatrixMode(GL_MODELVIEW);
312         glPushMatrix();
313         glLoadIdentity();
314
315         struct dtx_box bbox;
316         dtx_glyph_box('Q', &bbox);
317
318         // draw progress/status bar
319         bar_height = bbox.height + 4;
320         prog_width = show_progress ? status->progress_percent * win_width / 100 : 0;
321
322         glBegin(GL_QUADS);
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);
328
329         glColor4f(0.25, 0, 0, 1);
330         glVertex2f(0, 0);
331         glVertex2f(prog_width, 0);
332         glVertex2f(prog_width, bar_height);
333         glVertex2f(0, bar_height);
334         glEnd();
335
336         // draw the text
337         glTranslatef(bbox.x + 2, bbox.y + 2, 0);
338
339         glColor4f(1, 1, 1, 1);
340
341         if(opt_samples > 0) {
342                 dtx_printf("samples: %ld / %ld", status->samples, status->max_samples);
343
344                 glLoadIdentity();
345                 glTranslatef(win_width - dtx_string_width("progress: 100%") - 2, bbox.y + 2, 0);
346                 dtx_printf("progress: %ld%%", status->progress_percent);
347         } else {
348                 dtx_printf("samples: %ld", status->samples);
349         }
350
351         {
352                 // samples/sec display
353                 static unsigned long paths_per_sec, prev_msec, prev_paths;
354
355                 unsigned long msec = glutGet(GLUT_ELAPSED_TIME) - start_time;
356                 unsigned long dt = msec - prev_msec;
357
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;
362                         }
363                         prev_msec = msec;
364                         prev_paths = paths;
365                 }
366
367                 glLoadIdentity();
368                 glTranslatef((win_width - dtx_string_width("paths/s: 999999")) / 2, bbox.y + 2, 0);
369                 if(paths_per_sec) {
370                         dtx_printf("paths/s: %ld", paths_per_sec);
371                 } else {
372                         dtx_printf("paths/s: ???");
373                 }
374         }
375
376         glPopMatrix();
377         glMatrixMode(GL_PROJECTION);
378         glPopMatrix();
379 }
380
381 static void save_image(const char *fname)
382 {
383         float *fb = erb_get_framebuffer(erb);
384
385         if(img_save_pixels(fname ? fname : "output.png", fb, width, height, IMG_FMT_RGBAF) == -1) {
386                 fprintf(stderr, "failed to save image\n");
387         }
388 }
389
390 static void reshape(int x, int y)
391 {
392         glViewport(0, 0, x, y);
393         resize_rtarget(x, y);
394
395         erb_setopti(erb, ERB_OPT_WIDTH, width);
396         erb_setopti(erb, ERB_OPT_HEIGHT, height);
397 }
398
399 static void keyb(unsigned char key, int x, int y)
400 {
401         switch(key) {
402         case 27:
403                 if(con_is_visible()) {
404                         con_hide();
405                         glutPostRedisplay();
406                 } else {
407                         end_frame();
408                         exit(0);
409                 }
410                 break;
411
412         case ' ':
413                 if(!con_is_visible()) {
414                         begin_frame(0);
415                 } else {
416                         con_input_key(' ');
417                         glutPostRedisplay();
418                 }
419                 break;
420
421         case '`':
422                 con_set_visible(!con_is_visible());
423                 glutPostRedisplay();
424                 break;
425
426         case '~':
427                 show_status = !show_status;
428                 glutPostRedisplay();
429                 break;
430
431         default:
432                 // otherwise if the console is visible, let them through
433                 if(con_is_visible()) {
434                         con_input_key(key);
435                         glutPostRedisplay();
436                         return; // don't pass anything to the erb input handler
437                 }
438         }
439
440         /* TODO handle input */
441 }
442
443 static void keyb_up(unsigned char key, int x, int y)
444 {
445         /* TODO handle input */
446 }
447
448 static void skeyb(int key, int x, int y)
449 {
450         if(key == GLUT_KEY_F12) {
451                 printf("saving image...\n");
452                 save_image(0);
453                 return;
454         }
455
456         if(con_is_visible()) {
457                 switch(key) {
458                 case GLUT_KEY_F8:
459                         con_debug();
460                         return;
461
462                 case GLUT_KEY_LEFT:
463                         con_input_key(CON_KEY_LEFT);
464                         break;
465                 case GLUT_KEY_RIGHT:
466                         con_input_key(CON_KEY_RIGHT);
467                         break;
468                 case GLUT_KEY_UP:
469                         con_input_key(CON_KEY_UP);
470                         break;
471                 case GLUT_KEY_DOWN:
472                         con_input_key(CON_KEY_DOWN);
473                         break;
474                 case GLUT_KEY_HOME:
475                         con_input_key(CON_KEY_HOME);
476                         break;
477                 case GLUT_KEY_END:
478                         con_input_key(CON_KEY_END);
479                         break;
480                 case GLUT_KEY_INSERT:
481                         con_input_key(CON_KEY_INS);
482                         break;
483                 case GLUT_KEY_PAGE_UP:
484                         con_input_key(CON_KEY_PGUP);
485                         break;
486                 case GLUT_KEY_PAGE_DOWN:
487                         con_input_key(CON_KEY_PGDOWN);
488                         break;
489
490                 default:
491                         return;
492                 }
493                 glutPostRedisplay();
494         }
495 }
496
497 static void mouse(int bn, int st, int x, int y)
498 {
499         /* TODO handle input */
500 }
501
502 static void motion(int x, int y)
503 {
504         /* TODO handle input */
505 }
506
507 static int next_pow2(int x)
508 {
509         int res = 2;
510         while(res < x) {
511                 res <<= 1;
512         }
513         return res;
514 }
515
516 static void sighandler(int s)
517 {
518         exit(0);
519 }
520
521 static int parse_args(int argc, char **argv)
522 {
523         int i;
524
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]);
531                                         return -1;
532                                 }
533
534                         } else if(strcmp(argv[i], "-iter") == 0) {
535                                 opt_iter = atoi(argv[++i]);
536                                 if(opt_iter <= 0) {
537                                         fprintf(stderr, "invalid -iter option: %s\n", argv[i]);
538                                         return -1;
539                                 }
540
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]);
545                                         return -1;
546                                 }
547
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]);
552                                         return -1;
553                                 }
554
555                         } else {
556                                 fprintf(stderr, "invalid option: %s\n", argv[i]);
557                                 return -1;
558                         }
559                 } else {
560                         struct input_file *infile;
561
562                         if(!(infile = malloc(sizeof *infile))) {
563                                 fprintf(stderr, "failed to allocate input file node\n");
564                                 return -1;
565                         }
566                         infile->fname = argv[i];
567                         infile->next = 0;
568
569                         if(input_files_tail) {
570                                 input_files_tail->next = infile;
571                                 input_files_tail = infile;
572                         } else {
573                                 input_files = input_files_tail = infile;
574                         }
575                 }
576         }
577
578         return 0;
579 }
580
581 static void con_parse(const char *line, void *cls)
582 {
583         int len, i;
584         char *buf, *tok;
585         char *args[128];
586         int argc = 0;
587
588         if(!(len = strlen(line))) return;
589
590         buf = alloca(len + 1);
591         memcpy(buf, line, len + 1);
592
593         while((tok = strtok(buf, " \n\r\v\t"))) {
594                 buf = 0;
595                 args[argc++] = tok;
596         }
597         args[argc] = 0;
598
599         if(!args[0]) return;
600
601         if(strcmp(args[0], "exit") == 0 || strcmp(args[0], "quit") == 0) {
602                 exit(0);
603
604         } else if(strcmp(args[0], "clear") == 0) {
605                 erb_clear(erb);
606
607         } else if(strcmp(args[0], "stop") == 0) {
608                 erb_end_frame(erb);
609
610         } else if(strcmp(args[0], "render") == 0) {
611                 long tm = 0;
612                 if(args[1]) {
613                         char *endp;
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");
617                                 return;
618                         }
619                 }
620                 begin_frame(tm);
621
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);
626                 } else {
627                         con_printf("invalid samples command: %s\n", line);
628                 }
629
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]);
634                         } else {
635                                 begin_frame(0);
636                         }
637                 }
638
639                 /*
640         } else if(strcmp(args[0], "add") == 0) {
641                 if(erb_proc_cmd(erb, line + 4) == -1) {
642                         con_puts("invalid add command\n");
643                 } else {
644                         begin_frame(0);
645                 }
646                 */
647
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");
656
657         } else {
658                 con_printf("unrecognized command: %s\n", args[0]);
659         }
660 }