// -*- c++ -*- #ifndef UTIL_H #define UTIL_H #include <time.h> #include <stdio.h> #include <stdarg.h> #include "glextfuncs.h" #include "pstring.h" template<typename T> T min(T a, T b){ return a < b ? a : b; } template<typename T> T max(T a, T b){ return a > b ? a : b; } double time_wall_fp() { struct timespec now; clock_gettime(CLOCK_REALTIME,&now); return now.tv_sec + ((double)now.tv_nsec) * 1e-9; } double time_process_fp() { struct timespec now; clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&now); return now.tv_sec + ((double)now.tv_nsec) * 1e-9; } // Rename keys so a single namespace can be used for regular (ASCII) // keys and "special" ones. #define FB_KEY_F1 ( GLUT_KEY_F1 + 0x100 ) #define FB_KEY_F2 ( GLUT_KEY_F2 + 0x100 ) #define FB_KEY_F3 ( GLUT_KEY_F3 + 0x100 ) #define FB_KEY_F4 ( GLUT_KEY_F4 + 0x100 ) #define FB_KEY_F5 ( GLUT_KEY_F5 + 0x100 ) #define FB_KEY_F6 ( GLUT_KEY_F6 + 0x100 ) #define FB_KEY_F7 ( GLUT_KEY_F7 + 0x100 ) #define FB_KEY_F8 ( GLUT_KEY_F8 + 0x100 ) #define FB_KEY_F9 ( GLUT_KEY_F9 + 0x100 ) #define FB_KEY_F10 ( GLUT_KEY_F10 + 0x100 ) #define FB_KEY_F11 ( GLUT_KEY_F11 + 0x100 ) #define FB_KEY_F12 ( GLUT_KEY_F12 + 0x100 ) #define FB_KEY_LEFT ( GLUT_KEY_LEFT + 0x100 ) #define FB_KEY_UP ( GLUT_KEY_UP + 0x100 ) #define FB_KEY_RIGHT ( GLUT_KEY_RIGHT + 0x100 ) #define FB_KEY_DOWN ( GLUT_KEY_DOWN + 0x100 ) #define FB_KEY_PAGE_UP ( GLUT_KEY_PAGE_UP + 0x100 ) #define FB_KEY_PAGE_DOWN ( GLUT_KEY_PAGE_DOWN + 0x100 ) #define FB_KEY_HOME ( GLUT_KEY_HOME + 0x100 ) #define FB_KEY_END ( GLUT_KEY_END + 0x100 ) #define FB_KEY_INSERT ( GLUT_KEY_INSERT + 0x100 ) inline void pError_Exit() { exit(1); } inline void pError_Msg(char *msg) { fprintf(stderr,"User Error: %s\n",msg); pError_Exit(); } inline bool pError_Check(int error = -1) { const int err = glGetError(); if ( err == GL_NO_ERROR ) return false; if ( err == error ) return true; fprintf(stderr,"GL Error: %s\n",gluErrorString(err)); pError_Exit(); return true; // Unreachable. } #define P_GL_PRINT_STRING(token) lprint_string(token,#token); inline void lprint_string(int token, const char *name) { pError_Check(); char* const str = (char*)glGetString(token); if ( pError_Check(GL_INVALID_ENUM) ) printf("S %s: ** Unrecognized**\n",name); else printf("S %s: \"%s\"\n",name,str); } #define PRINT_ATTRIBUTE(token) lprint_attribute(token,#token); inline void lprint_attribute(int token, const char *name) { pError_Check(); int val; glGetIntegerv(token,&val); if ( pError_Check(GL_INVALID_ENUM) ) printf("Attribute %s: ** Unrecognized **\n",name); else printf("Attribute %s: %d\n",name,val); } class pOpenGL_Helper; pOpenGL_Helper* opengl_helper_self_ = NULL; class pFrame_Timer { public: pFrame_Timer():inited(false),work_description(NULL) { query_timer_id = 0; frame_group_size = 10; frame_rate = 0; cpu_frac = 0; } void work_unit_set(const char *description, double multiplier = 1) { work_multiplier = multiplier; work_accum = 0; work_description = strdup(description); } void work_amt_set(int amt){ work_accum += amt; } void init(); void frame_start(); void frame_end(); const char* frame_rate_text_get() const { return frame_rate_text.s; } int frame_group_size; private: void frame_rate_group_start(); void var_reset() { frame_group_count = 0; cpu_tsum = tsum = 0; work_accum = 0; } bool inited; double frame_group_start_time; int frame_group_count; double tsum, tlast, cpu_tsum, cpu_tlast; double work_accum; double work_multiplier; int work_count_last; char *work_description; double work_rate; double frame_rate; double cpu_frac; double time_render_start; GLuint query_timer_id; uint xfcount; // Frame count provided by glx. pString frame_rate_text; }; void pFrame_Timer::init() { inited = true; if ( glutExtensionSupported("GL_EXT_timer_query") ) glGenQueries(1,&query_timer_id); frame_group_start_time = time_wall_fp(); var_reset(); frame_rate_group_start(); } void pFrame_Timer::frame_rate_group_start() { const double last_wall_time = frame_group_start_time; const double last_frame_count = max(frame_group_count,1); const double last_frame_count_inv = 1.0 / last_frame_count; frame_group_start_time = time_wall_fp(); const double group_duration = frame_group_start_time - last_wall_time; tlast = 1e-6 * tsum * last_frame_count_inv; cpu_tlast = cpu_tsum * last_frame_count_inv; frame_rate = last_frame_count / group_duration; cpu_frac = cpu_tsum / group_duration; if ( work_description ) { work_rate = work_multiplier * work_accum / group_duration; } var_reset(); } void pFrame_Timer::frame_start() { if ( !inited ) init(); pError_Check(); if ( query_timer_id ) glBeginQuery(GL_TIME_ELAPSED_EXT,query_timer_id); pError_Check(); time_render_start = time_process_fp(); if ( frame_group_count++ >= frame_group_size ) frame_rate_group_start(); } void pFrame_Timer::frame_end() { const double time_render_elapsed = time_process_fp() - time_render_start; if ( query_timer_id ) { glEndQuery(GL_TIME_ELAPSED_EXT); int timer_val = 0; glGetQueryObjectiv(query_timer_id,GL_QUERY_RESULT,&timer_val); tsum += timer_val; } cpu_tsum += time_render_elapsed; const uint xfcount_prev = xfcount; if ( ptr_glXGetVideoSyncSGI ) ptr_glXGetVideoSyncSGI(&xfcount); frame_rate_text = ""; frame_rate_text.sprintf("FPS: %.2f XF ", frame_rate); if ( ptr_glXGetVideoSyncSGI ) frame_rate_text.sprintf("%2d", xfcount - xfcount_prev ); else frame_rate_text += "--"; frame_rate_text += " GPU "; if ( query_timer_id ) frame_rate_text.sprintf("%.3f us",tlast); else frame_rate_text += "---"; frame_rate_text.sprintf (" CPU %.2f ms (%.1f%%)", 1000 * cpu_tlast, 100 * cpu_frac); if ( work_description ) frame_rate_text.sprintf(" %s %.3f", work_description, work_rate); } struct pVariable_Control_Elt {float *var; char *name;}; class pVariable_Control { public: pVariable_Control() { size = 0; storage = (pVariable_Control_Elt*)malloc(0); current = NULL; } void insert(float &var, const char *name) { size++; const int cidx = current - storage; storage = (pVariable_Control_Elt*)realloc(storage,size*sizeof(*storage)); pVariable_Control_Elt* const elt = &storage[size-1]; elt->var = &var; elt->name = strdup(name); current = &storage[ size == 1 ? 0 : cidx ]; } void adjust_higher() {if ( current ) current->var[0] *= 1.05;} void adjust_lower() {if ( current ) current->var[0] *= 0.95;} void switch_var_right() { if ( !current ) return; current++; if ( current == &storage[size] ) current = storage; } int size; pVariable_Control_Elt *storage, *current; }; class pOpenGL_Helper { public: pOpenGL_Helper(int& argc, char** argv) { opengl_helper_self_ = this; width = height = 0; frame_period = -1; // No timer callback. next_frame_time = 0; cb_keyboard(); init_gl(argc, argv); } ~pOpenGL_Helper(){} void rate_set(double frames_per_second) { frame_period = 1.0 / frames_per_second; } double next_frame_time, frame_period; static void cb_timer_w(int data){ opengl_helper_self_->cbTimer(data); } void cbTimer(int data) { glutPostRedisplay(); if ( frame_period < 0 ) return; if ( next_frame_time == 0 ) next_frame_time = time_wall_fp(); const double now = time_wall_fp(); next_frame_time += frame_period; const double delta = next_frame_time - now; const int delta_ms = delta <= 0 ? 0 : int(delta * 1000); glutTimerFunc(delta_ms,cb_timer_w,0); } // Use DISPLAY_FUNC to write frame buffer. // void display_cb_set (void (*display_func)(void *), void *data) { user_display_func = display_func; user_display_data = data; glutDisplayFunc(&cb_display_w); glutKeyboardFunc(&cb_keyboard_w); glutSpecialFunc(&cb_keyboard_special_w); cbTimer(0); glutMainLoop(); } // Return width and height of frame buffer. // int get_width() { return width; } int get_height() { return height; } // Key pressed by user since last call of DISPLAY_FUNC. // ASCII value, one of the FB_KEY_XXXX macros below, or 0 if // no key pressed. // int keyboard_key; int keyboard_x, keyboard_y; // Mouse location when key pressed. // Print text in frame buffer, starting at upper left. // Arguments same as printf. // void fbprintf(const char* fmt, ...) { va_list ap; pString str; va_start(ap,fmt); str.vsprintf(fmt,ap); va_end(ap); glutBitmapString(GLUT_BITMAP_HELVETICA_12,(unsigned char*)str.s); } private: void init_gl(int& argc, char** argv) { exe_file_name = argv && argv[0] ? argv[0] : "unknown name"; glutInit(&argc, argv); lglext_ptr_init(); glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ); glutInitWindowSize(640,480); pStringF title("OpenGL Demo - %s",exe_file_name); glut_window_id = glutCreateWindow(title); // Note: These functions don't work before a window is created. // P_GL_PRINT_STRING(GL_VENDOR); P_GL_PRINT_STRING(GL_RENDERER); P_GL_PRINT_STRING(GL_VERSION); } static void cb_display_w(void){ opengl_helper_self_->cb_display(); } void cb_display(void) { shape_update(); glWindowPos2i(10,height-20); user_display_func(user_display_data); cb_keyboard(); } void shape_update() { const int width_new = glutGet(GLUT_WINDOW_WIDTH); const int height_new = glutGet(GLUT_WINDOW_HEIGHT); width = width_new; height = height_new; } static void cb_keyboard_w(unsigned char key, int x, int y) {opengl_helper_self_->cb_keyboard(key,x,y);} static void cb_keyboard_special_w(int key, int x, int y) {opengl_helper_self_->cb_keyboard(key+0x100,x,y);} void cb_keyboard(int key=0, int x=0, int y=0) { keyboard_key = key; keyboard_x = x; keyboard_y = y; if ( !key ) return; if ( keyboard_key == FB_KEY_F12 ) { write_img(); return; } glutPostRedisplay(); } void write_img() { pStringF pipe_name("pnmtopng > %s.png",exe_file_name); FILE* const fp = popen(pipe_name, "w"); if ( !fp ) { fprintf(stderr, "Could not open pipe for screenshot.\n"); return; } fprintf(fp,"P6\n%d %d 255\n",width,height); glReadBuffer(GL_FRONT_LEFT); const int size = width * height; char* const pbuffer = (char*) malloc(size * 3); glReadPixels(0,0,width,height,GL_RGB,GL_UNSIGNED_BYTE,pbuffer); for ( int y=height-1; y>=0; y-- ) { char* row = &pbuffer[ y * width * 3 ]; for ( int x=0; x<width; x++ ) { putc(row[0],fp); putc(row[1],fp); putc(row[2],fp); row += 3; } } pclose(fp); free(pbuffer); } private: const char* exe_file_name; double render_start; int width; int height; // Height of simulated frame buffer, not displayed window. int glut_window_id; void (*user_display_func)(void *data); void *user_display_data; }; #endif