nuclear@2: #include nuclear@2: #include nuclear@34: #include nuclear@2: #include nuclear@29: #include nuclear@19: #include nuclear@26: #include nuclear@29: #include nuclear@32: #include nuclear@2: #include "opengl.h" nuclear@4: #include "erebus.h" nuclear@37: #include "console.h" nuclear@2: nuclear@26: using namespace std::chrono; nuclear@26: nuclear@2: static bool init(); nuclear@2: static void cleanup(); nuclear@32: static void begin_frame(long tm); nuclear@32: static void end_frame(); nuclear@2: static void resize_rtarget(int xsz, int ysz); nuclear@2: static void update_rect(int x, int y, int xsz, int ysz, float *pixels); nuclear@4: static void idle(); nuclear@2: static void display(); nuclear@32: static void display_statusbar(const erb_render_status &status); nuclear@29: static void save_image(const char *fname = 0); nuclear@2: static void reshape(int x, int y); nuclear@2: static void keyb(unsigned char key, int x, int y); nuclear@9: static void keyb_up(unsigned char key, int x, int y); nuclear@37: static void skeyb(int key, int x, int y); nuclear@2: static void mouse(int bn, int st, int x, int y); nuclear@9: static void motion(int x, int y); nuclear@9: static void sball_button(int bn, int st); nuclear@9: static void sball_motion(int x, int y, int z); nuclear@2: static int next_pow2(int x); nuclear@29: static void sighandler(int s); nuclear@32: static bool parse_args(int argc, char **argv); nuclear@37: static void con_parse(const char *line); nuclear@2: nuclear@32: static int win_width, win_height, width, height, rtex_width, rtex_height; nuclear@2: static unsigned int rtex; nuclear@2: nuclear@32: static int opt_samples = -1; nuclear@32: static int opt_iter = -1; nuclear@32: static int opt_threads = -1; nuclear@32: static float opt_imgscale = 2.0f; nuclear@32: nuclear@4: static erebus *erb; nuclear@4: static bool render_pending; nuclear@32: static bool show_status = true; nuclear@32: static steady_clock::time_point start_time; nuclear@4: nuclear@19: static std::vector sfiles; nuclear@4: nuclear@37: #define FONTSZ 22 nuclear@32: static dtx_font *font; nuclear@37: static Console con; nuclear@32: nuclear@2: int main(int argc, char **argv) nuclear@2: { nuclear@2: glutInitWindowSize(1024, 600); nuclear@2: glutInit(&argc, argv); nuclear@19: nuclear@32: if(!parse_args(argc, argv)) { nuclear@32: return 1; nuclear@19: } nuclear@19: nuclear@2: glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); nuclear@2: glutCreateWindow("erebus OpenGL frontend"); nuclear@2: nuclear@2: glutDisplayFunc(display); nuclear@2: glutReshapeFunc(reshape); nuclear@2: glutKeyboardFunc(keyb); nuclear@9: glutKeyboardUpFunc(keyb_up); nuclear@37: glutSpecialFunc(skeyb); nuclear@2: glutMouseFunc(mouse); nuclear@9: glutMotionFunc(motion); nuclear@9: glutSpaceballButtonFunc(sball_button); nuclear@9: glutSpaceballMotionFunc(sball_motion); nuclear@2: nuclear@2: if(!init()) { nuclear@2: return 1; nuclear@2: } nuclear@2: atexit(cleanup); nuclear@29: signal(SIGINT, sighandler); nuclear@29: signal(SIGSEGV, sighandler); nuclear@29: signal(SIGILL, sighandler); nuclear@29: signal(SIGTERM, sighandler); nuclear@29: signal(SIGFPE, sighandler); nuclear@2: nuclear@2: glutMainLoop(); nuclear@2: } nuclear@2: nuclear@2: static bool init() nuclear@2: { nuclear@32: width = glutGet(GLUT_WINDOW_WIDTH) / opt_imgscale; nuclear@32: height = glutGet(GLUT_WINDOW_HEIGHT) / opt_imgscale; nuclear@32: nuclear@37: //if(!(font = dtx_open_font("/usr/share/fonts/opentype/linux-libertine/LinLibertine_R.otf", FONTSZ))) { nuclear@32: if(!(font = dtx_open_font_glyphmap("data/serif.glyphmap"))) { nuclear@32: fprintf(stderr, "warning: failed to load font!\n"); nuclear@32: } nuclear@5: nuclear@37: //dtx_font *confont = dtx_open_font("/usr/share/fonts/truetype/droid/DroidSansMono.ttf", 14); nuclear@37: dtx_font *confont = dtx_open_font_glyphmap("data/mono.glyphmap"); nuclear@37: if(confont) { nuclear@37: con.set_font(confont, 14); nuclear@37: } else { nuclear@37: con.set_font(font, FONTSZ); nuclear@37: } nuclear@37: con.set_command_func(con_parse); nuclear@37: nuclear@4: if(!(erb = erb_init())) { nuclear@4: return false; nuclear@4: } nuclear@4: erb_setopti(erb, ERB_OPT_WIDTH, width); nuclear@4: erb_setopti(erb, ERB_OPT_HEIGHT, height); nuclear@4: nuclear@32: if(opt_samples != -1) { nuclear@32: erb_setopti(erb, ERB_OPT_MAX_SAMPLES, opt_samples); nuclear@32: } nuclear@32: if(opt_iter != -1) { nuclear@32: erb_setopti(erb, ERB_OPT_MAX_ITER, opt_iter); nuclear@32: } nuclear@32: if(opt_threads != -1) { nuclear@32: erb_setopti(erb, ERB_OPT_NUM_THREADS, opt_threads); nuclear@32: } nuclear@32: nuclear@19: for(size_t i=0; i(dur).count(); nuclear@32: long msec, sec, min, hr, days; nuclear@32: nuclear@32: msec = full_msec; nuclear@32: printf("done in "); nuclear@32: if((sec = msec / 1000) > 0) { nuclear@32: msec %= 1000; nuclear@32: if((min = sec / 60) > 0) { nuclear@32: sec %= 60; nuclear@32: if((hr = min / 60) > 0) { nuclear@32: min %= 60; nuclear@32: if((days = hr / 24) > 0) { nuclear@32: hr %= 24; nuclear@32: printf("%ld days ", days); nuclear@32: } nuclear@32: printf("%ld hours ", hr); nuclear@32: } nuclear@32: printf("%ld min ", min); nuclear@32: } nuclear@32: printf("%ld sec ", sec); nuclear@32: } nuclear@32: printf("%ld ms (%ld total msec)\n", msec, full_msec); nuclear@32: nuclear@32: render_pending = false; nuclear@32: glutIdleFunc(0); nuclear@32: } nuclear@32: nuclear@2: static void resize_rtarget(int xsz, int ysz) nuclear@2: { nuclear@2: static unsigned char *defpix; nuclear@2: nuclear@32: win_width = xsz; nuclear@32: win_height = ysz; nuclear@32: nuclear@32: width = xsz / opt_imgscale; nuclear@32: height = ysz / opt_imgscale; nuclear@2: nuclear@8: if(width <= rtex_width && height <= rtex_height) { nuclear@2: return; nuclear@2: } nuclear@8: rtex_width = next_pow2(width); nuclear@8: rtex_height = next_pow2(height); nuclear@2: nuclear@2: printf("resizing framebuffer texture: %dx%d\n", rtex_width, rtex_height); nuclear@2: nuclear@2: if(!rtex) { nuclear@2: glGenTextures(1, &rtex); nuclear@2: } nuclear@2: nuclear@2: delete [] defpix; nuclear@2: defpix = new unsigned char[rtex_width * rtex_height * 4]; nuclear@2: unsigned char *ptr = defpix; nuclear@2: for(int i=0; i> 4) & 1) == ((j >> 4) & 1); nuclear@2: nuclear@2: int val = chess ? 64 : 48; nuclear@2: nuclear@2: *ptr++ = val; nuclear@2: *ptr++ = val; nuclear@2: *ptr++ = val; nuclear@2: *ptr++ = 255; nuclear@2: } nuclear@2: } nuclear@2: nuclear@2: glBindTexture(GL_TEXTURE_2D, rtex); nuclear@2: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); nuclear@2: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); nuclear@2: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, rtex_width, rtex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, defpix); nuclear@2: } nuclear@2: nuclear@2: static void update_rect(int x, int y, int xsz, int ysz, float *pixels) nuclear@2: { nuclear@2: glBindTexture(GL_TEXTURE_2D, rtex); nuclear@2: glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, xsz, ysz, GL_RGBA, GL_FLOAT, pixels); nuclear@2: } nuclear@2: nuclear@4: static void idle() nuclear@4: { nuclear@4: glutPostRedisplay(); nuclear@4: } nuclear@4: nuclear@2: static void display() nuclear@2: { nuclear@32: static struct erb_render_status status; nuclear@32: nuclear@4: if(render_pending) { nuclear@8: if(erb_render(erb, 64) == 0) { nuclear@32: end_frame(); nuclear@4: } nuclear@4: update_rect(0, 0, width, height, erb_get_framebuffer(erb)); nuclear@32: erb_get_status(erb, &status); nuclear@4: } nuclear@4: nuclear@2: float maxu = (float)width / (float)rtex_width; nuclear@2: float maxv = (float)height / (float)rtex_height; nuclear@2: nuclear@34: glEnable(GL_TEXTURE_2D); nuclear@34: glBindTexture(GL_TEXTURE_2D, rtex); nuclear@34: nuclear@2: glBegin(GL_QUADS); nuclear@32: glColor4f(1, 1, 1, 1); nuclear@2: glTexCoord2f(0, maxv); glVertex2f(-1, -1); nuclear@2: glTexCoord2f(maxu, maxv); glVertex2f(1, -1); nuclear@2: glTexCoord2f(maxu, 0); glVertex2f(1, 1); nuclear@2: glTexCoord2f(0, 0); glVertex2f(-1, 1); nuclear@2: glEnd(); nuclear@2: nuclear@37: // draw the console nuclear@37: con.update(); nuclear@37: con.draw(); nuclear@37: nuclear@32: // draw progress information etc... nuclear@32: if(show_status) { nuclear@32: display_statusbar(status); nuclear@32: } nuclear@32: nuclear@2: glutSwapBuffers(); nuclear@2: assert(glGetError() == GL_NO_ERROR); nuclear@2: } nuclear@2: nuclear@32: static void display_statusbar(const erb_render_status &status) nuclear@32: { nuclear@32: if(!font) return; nuclear@37: dtx_use_font(font, FONTSZ); nuclear@32: nuclear@32: bool show_progress = opt_samples > 0; nuclear@32: nuclear@32: glDisable(GL_TEXTURE_2D); nuclear@32: nuclear@32: glMatrixMode(GL_PROJECTION); nuclear@32: glPushMatrix(); nuclear@32: glLoadIdentity(); nuclear@32: glOrtho(0, win_width, 0, win_height, -1, 1); nuclear@32: nuclear@32: glMatrixMode(GL_MODELVIEW); nuclear@32: glPushMatrix(); nuclear@32: glLoadIdentity(); nuclear@32: nuclear@34: dtx_box bbox; nuclear@34: dtx_glyph_box('Q', &bbox); nuclear@34: nuclear@34: // draw progress/status bar nuclear@34: int bar_height = bbox.height + 4; nuclear@32: int prog_width = show_progress ? status.progress_percent * win_width / 100 : 0; nuclear@32: nuclear@32: glBegin(GL_QUADS); nuclear@32: glColor4f(0, 0, 0, 1); nuclear@32: glVertex2f(prog_width, 0); nuclear@32: glVertex2f(win_width, 0); nuclear@34: glVertex2f(win_width, bar_height); nuclear@34: glVertex2f(prog_width, bar_height); nuclear@32: nuclear@32: glColor4f(0.25, 0, 0, 1); nuclear@32: glVertex2f(0, 0); nuclear@32: glVertex2f(prog_width, 0); nuclear@34: glVertex2f(prog_width, bar_height); nuclear@34: glVertex2f(0, bar_height); nuclear@32: glEnd(); nuclear@32: nuclear@34: // draw the text nuclear@34: glTranslatef(bbox.x + 2, bbox.y + 2, 0); nuclear@32: nuclear@32: glColor4f(1, 1, 1, 1); nuclear@32: nuclear@32: if(opt_samples > 0) { nuclear@34: dtx_printf("samples: %ld / %ld", status.samples, status.max_samples); nuclear@32: nuclear@34: glLoadIdentity(); nuclear@34: glTranslatef(win_width - dtx_string_width("progress: 100%") - 2, bbox.y + 2, 0); nuclear@34: dtx_printf("progress: %ld%%", status.progress_percent); nuclear@32: } else { nuclear@34: dtx_printf("samples: %ld", status.samples); nuclear@32: } nuclear@34: nuclear@34: // samples/sec display nuclear@34: static long paths_per_sec, prev_msec, prev_paths; nuclear@34: nuclear@34: long msec = duration_cast(steady_clock::now() - start_time).count(); nuclear@34: long dt = msec - prev_msec; nuclear@34: nuclear@34: if(dt >= 1500) { // average over 1.5 seconds nuclear@34: long paths = status.samples * width * height; nuclear@34: if(prev_msec > 0 && prev_paths <= paths) { // check valid interval (not a restart or whatever) nuclear@34: paths_per_sec = 1000 * (paths - prev_paths) / dt; nuclear@34: } nuclear@34: prev_msec = msec; nuclear@34: prev_paths = paths; nuclear@34: } nuclear@34: nuclear@34: glLoadIdentity(); nuclear@34: glTranslatef((win_width - dtx_string_width("paths/s: 999999")) / 2, bbox.y + 2, 0); nuclear@34: if(paths_per_sec) { nuclear@34: dtx_printf("paths/s: %ld", paths_per_sec); nuclear@34: } else { nuclear@34: dtx_printf("paths/s: ???"); nuclear@34: } nuclear@34: nuclear@32: glPopMatrix(); nuclear@32: glMatrixMode(GL_PROJECTION); nuclear@32: glPopMatrix(); nuclear@32: } nuclear@32: nuclear@29: static void save_image(const char *fname) nuclear@29: { nuclear@29: float *fb = erb_get_framebuffer(erb); nuclear@29: nuclear@29: if(img_save_pixels(fname ? fname : "output.png", fb, width, height, IMG_FMT_RGBAF) == -1) { nuclear@29: fprintf(stderr, "failed to save image\n"); nuclear@29: } nuclear@29: } nuclear@29: nuclear@2: static void reshape(int x, int y) nuclear@2: { nuclear@2: glViewport(0, 0, x, y); nuclear@2: resize_rtarget(x, y); nuclear@4: nuclear@4: erb_setopti(erb, ERB_OPT_WIDTH, width); nuclear@4: erb_setopti(erb, ERB_OPT_HEIGHT, height); nuclear@2: } nuclear@2: nuclear@2: static void keyb(unsigned char key, int x, int y) nuclear@2: { nuclear@2: switch(key) { nuclear@2: case 27: nuclear@37: if(con.is_visible()) { nuclear@37: con.hide(); nuclear@37: glutPostRedisplay(); nuclear@37: } else { nuclear@37: end_frame(); nuclear@37: exit(0); nuclear@37: } nuclear@37: break; nuclear@4: nuclear@4: case ' ': nuclear@37: if(!con.is_visible()) { nuclear@37: begin_frame(0); nuclear@37: } else { nuclear@37: con.input_key(' '); nuclear@37: glutPostRedisplay(); nuclear@37: } nuclear@29: break; nuclear@32: nuclear@34: case '`': nuclear@37: con.set_visible(!con.is_visible()); nuclear@37: glutPostRedisplay(); nuclear@37: break; nuclear@37: nuclear@37: case '~': nuclear@32: show_status = !show_status; nuclear@32: glutPostRedisplay(); nuclear@32: break; nuclear@37: nuclear@37: default: nuclear@37: // otherwise if the console is visible, let them through nuclear@37: if(con.is_visible()) { nuclear@37: con.input_key(key); nuclear@37: glutPostRedisplay(); nuclear@37: return; // don't pass anything to the erb input handler nuclear@37: } nuclear@2: } nuclear@9: nuclear@10: if(erb_input_keyboard(erb, key, true)) { nuclear@9: glutPostRedisplay(); nuclear@9: } nuclear@9: } nuclear@9: nuclear@9: static void keyb_up(unsigned char key, int x, int y) nuclear@9: { nuclear@10: if(erb_input_keyboard(erb, key, false)) { nuclear@9: glutPostRedisplay(); nuclear@9: } nuclear@2: } nuclear@2: nuclear@37: static void skeyb(int key, int x, int y) nuclear@37: { nuclear@37: if(key == GLUT_KEY_F12) { nuclear@37: printf("saving image...\n"); nuclear@37: save_image(); nuclear@37: return; nuclear@37: } nuclear@37: nuclear@37: if(con.is_visible()) { nuclear@37: switch(key) { nuclear@37: case GLUT_KEY_F8: nuclear@37: con.debug(); nuclear@37: return; nuclear@37: nuclear@37: case GLUT_KEY_LEFT: nuclear@37: con.input_key(Console::KEY_LEFT); nuclear@37: break; nuclear@37: case GLUT_KEY_RIGHT: nuclear@37: con.input_key(Console::KEY_RIGHT); nuclear@37: break; nuclear@37: case GLUT_KEY_UP: nuclear@37: con.input_key(Console::KEY_UP); nuclear@37: break; nuclear@37: case GLUT_KEY_DOWN: nuclear@37: con.input_key(Console::KEY_DOWN); nuclear@37: break; nuclear@37: case GLUT_KEY_HOME: nuclear@37: con.input_key(Console::KEY_HOME); nuclear@37: break; nuclear@37: case GLUT_KEY_END: nuclear@37: con.input_key(Console::KEY_END); nuclear@37: break; nuclear@37: case GLUT_KEY_INSERT: nuclear@37: con.input_key(Console::KEY_INS); nuclear@37: break; nuclear@37: case GLUT_KEY_PAGE_UP: nuclear@37: con.input_key(Console::KEY_PGUP); nuclear@37: break; nuclear@37: case GLUT_KEY_PAGE_DOWN: nuclear@37: con.input_key(Console::KEY_PGDOWN); nuclear@37: break; nuclear@37: nuclear@37: default: nuclear@37: return; nuclear@37: } nuclear@37: glutPostRedisplay(); nuclear@37: } nuclear@37: } nuclear@37: nuclear@2: static void mouse(int bn, int st, int x, int y) nuclear@2: { nuclear@10: if(erb_input_mouse_button(erb, bn - GLUT_LEFT_BUTTON, st == GLUT_DOWN, x, y)) { nuclear@9: glutPostRedisplay(); nuclear@9: } nuclear@9: } nuclear@9: nuclear@9: static void motion(int x, int y) nuclear@9: { nuclear@15: if(erb_input_mouse_motion(erb, x, y)) { nuclear@9: glutPostRedisplay(); nuclear@9: } nuclear@9: } nuclear@9: nuclear@9: static void sball_button(int bn, int state) nuclear@9: { nuclear@10: if(erb_input_6dof_button(erb, bn, state == GLUT_DOWN)) { nuclear@9: glutPostRedisplay(); nuclear@9: } nuclear@9: } nuclear@9: nuclear@9: static void sball_motion(int x, int y, int z) nuclear@9: { nuclear@10: if(erb_input_6dof_motion(erb, x / 65536.0, y / 65536.0, z / 65536.0)) { nuclear@9: glutPostRedisplay(); nuclear@9: } nuclear@2: } nuclear@2: nuclear@2: static int next_pow2(int x) nuclear@2: { nuclear@2: int res = 2; nuclear@2: while(res < x) { nuclear@2: res <<= 1; nuclear@2: } nuclear@2: return res; nuclear@2: } nuclear@29: nuclear@29: static void sighandler(int s) nuclear@29: { nuclear@29: exit(0); nuclear@29: } nuclear@32: nuclear@32: static bool parse_args(int argc, char **argv) nuclear@32: { nuclear@32: for(int i=1; i args; nuclear@39: char *tok; nuclear@39: nuclear@39: while((tok = strtok(buf, " \n\r\v\t"))) { nuclear@39: buf = 0; nuclear@39: args.push_back(tok); nuclear@39: } nuclear@39: args.push_back(0); nuclear@39: int argc = args.size() - 1; nuclear@39: nuclear@40: if(!args[0]) return; nuclear@40: nuclear@40: if(strcmp(args[0], "exit") == 0 || strcmp(args[0], "quit") == 0) { nuclear@39: exit(0); nuclear@40: nuclear@40: } else if(strcmp(args[0], "clear") == 0) { nuclear@40: erb_clear(erb); nuclear@40: nuclear@40: } else if(strcmp(args[0], "stop") == 0) { nuclear@40: erb_end_frame(erb); nuclear@40: nuclear@40: } else if(strcmp(args[0], "render") == 0) { nuclear@40: long tm = 0; nuclear@40: if(args[1]) { nuclear@40: char *endp; nuclear@40: tm = strtol(args[1], &endp, 10); nuclear@40: if(endp == args[1]) { nuclear@40: con.printf("the argument to render must be a time value in milliseconds\n"); nuclear@40: return; nuclear@40: } nuclear@40: } nuclear@40: begin_frame(tm); nuclear@40: nuclear@39: } else if(strcmp(args[0], "load") == 0) { nuclear@39: for(int i=1; i