// -*- 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