Programming OpenGL in Linux: Programming Animations with GLX and Xlib
Programming Animations with OpenGL and Xlib[edit]
In section Programming OpenGL in Linux: GLX and Xlib a framework for an event-driven application is provided. All events concerning the application are caught in an event loop like this:
Display *dpy; XEvent xev; while(true) { XNextEvent(dpy, &xev); if(xev.type == Expose) ExposureFunction(); else if(xev.type == KeyPress) KeyboardFunction(); }
The event loop uses the XNextEvent function, which makes the application sleep until an event occurs (e.g. user pressing a key on the keyboard, user moving the mouse etc.) with the advantage of consuming very little cpu time when there is no user input. The user can have many applications running at the same time; only the one he is working with will put a load on the cpu (assuming that the others are minimized so they do not need to update their window contents).
This event-driven model is good for many types of applications, like editors, but cannot be used for animations or screensavers. An animation application, for instance, does not wait until it receives an exposure event; it wants to display a 3D scene at a high framerate.
At the end of this section, a framework for this task is provided. The program will show a rotating cube and display the framerate. The cube's rotation can be controlled using the <up/down/left/right> cursor keys. The program looks like this:
Main Loop[edit]
int main(int argc, char *argv[]){ CreateWindow(); SetupGL(); InitTimeCounter(); while(true) { UpdateTimeCounter(); CalculateFPS(); RotateCube(); ExposeFunc(); CheckEvents(); usleep(1000); } }
At program start, an OpenGL capable window with a GL context is created, and the time counter is initialized. Then an infinite loop is started, which updates the framerate, animates and displays the cube and checks for keyboard input (the usleep function is used to reduce cpu load. it can be omitted).
OpenGL Setup[edit]
void CreateWindow(); void SetupGL();
These functions create an OpenGL capable window and a GL context. A loop within the SetupGL function calls XLoadQueryFont to load an adobe courier font with a size between 14 and 32 pixels which can be used for glXUseXFont. If there is no such font installed on your system, there will be no text output in the gl window. In that case, use the xlsfonts command to find a suitable font and change the line
sprintf(font_string, "-adobe-courier-*-r-normal--%i-*", font_size);
to match a font that is installed on your system, for instance
sprintf(font_string, "-adobe-times-*-r-normal--%i-*", font_size);
The Animated Object[edit]
void DrawCube(float size); void ExposeFunc(); void RotateCube();
The main loop calls ExposeFunc, which prepares the scene by setting the projection and modelview matrix. Then the DrawCube func does its work, and afterwards time and framerate are displayed.
There are two values which control the cube's rotation: rot_y_vel and rot_z_vel, the rotation velocities around the global Y and Z axes. With the time difference DT between two consecutive frames, the cube rotates rot_y_vel*DT around the Y axis and rot_z_vel*DT around the Z axis. This is done in the RotateCube func. Note that the "rotation history" is stored in the rotation_matrix variable.
Time and Frame Counter[edit]
void InitTimeCounter(); void UpdateTimeCounter(); void CalculateFPS();
A time and frame counter can be realized using the gettimeofday function, which fills a timeval structure. At program start, InitTimeCounter is called, which calls gettimeofday to store the initial time value in timeval tv0. The UpdateTimeCounter function is called each time the program runs through the loop. It stores the current time in timeval tv, calculates the seconds elapsed since program start with reference to timeval tv0 and stores them in TimeCounter. For smooth animation, it is also necessary to calculate the time DT between two consecutive frames.
User Interaction[edit]
void CheckKeyboard();
The CheckKeyboard function is called to test for user input. The XCheckWindowEvent is used instead of XNextEvent, because it does not pause the program when there is not event. Note a slight difference: XCheckWindowEvent uses event masks (KeyPressEvent), while XNextEvent returns event types (KeyPress) in an XEvent structure.
Compilation[edit]
Assuming you are using gcc, you have to type something like
gcc -o animation animation.cc -L/usr/X11R6/lib/ -lGL -lGLU -lX11
Depending on your system, you may have to change the library path flag -L or add some more library flags like -lstdc++ -lm etc.
On Ubuntu and Ubuntu-related distributions, the following command:
gcc -o animation animation.cc -lX11 -lGL -lGLU
Should be enough to compile the program and get it to run correctly. Also, please make sure you have g++ installed on your machine, as the ".cc" extension indicates that the source of your program is a "C++" file.
Program Source[edit]
// -- Written in C++ -- // #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<math.h> #include<time.h> #include<sys/time.h> #include<X11/Xlib.h> #include<X11/XKBlib.h> #include<GL/glx.h> #include<GL/glext.h> #include<GL/glu.h> ////////////////////////////////////////////////////////////////////////////////// // GLOBAL IDENTIFIERS // ////////////////////////////////////////////////////////////////////////////////// Display *dpy; Window root, win; GLint att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; XVisualInfo *vi; GLXContext glc; Colormap cmap; XSetWindowAttributes swa; XWindowAttributes wa; XEvent xev; float TimeCounter, LastFrameTimeCounter, DT, prevTime = 0.0, FPS; struct timeval tv, tv0; int Frame = 1, FramesPerFPS; GLfloat rotation_matrix[16]; float rot_z_vel = 50.0, rot_y_vel = 30.0; ////////////////////////////////////////////////////////////////////////////////// // DRAW A CUBE // ////////////////////////////////////////////////////////////////////////////////// void DrawCube(float size) { glBegin(GL_QUADS); glColor3f(0.7, 0.0, 0.0); glVertex3f(-size, -size, -size); glVertex3f( size, -size, -size); glVertex3f( size, size, -size); glVertex3f(-size, size, -size); glVertex3f(-size, -size, size); glVertex3f( size, -size, size); glVertex3f( size, size, size); glVertex3f(-size, size, size); glColor3f(0.0, 0.0, 0.7); glVertex3f(-size, -size, -size); glVertex3f(-size, -size, size); glVertex3f(-size, size, size); glVertex3f(-size, size, -size); glVertex3f( size, -size, -size); glVertex3f( size, -size, size); glVertex3f( size, size, size); glVertex3f( size, size, -size); glColor3f(0.0, 0.7, 0.0); glVertex3f(-size, -size, -size); glVertex3f(-size, -size, size); glVertex3f( size, -size, size); glVertex3f( size, -size, -size); glVertex3f(-size, size, -size); glVertex3f(-size, size, size); glVertex3f( size, size, size); glVertex3f( size, size, -size); glEnd(); } ////////////////////////////////////////////////////////////////////////////////// // ROTATE THE CUBE // ////////////////////////////////////////////////////////////////////////////////// void RotateCube() { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(rot_y_vel*DT, 0.0, 1.0, 0.0); glRotatef(rot_z_vel*DT, 0.0, 0.0, 1.0); glMultMatrixf(rotation_matrix); glGetFloatv(GL_MODELVIEW_MATRIX, rotation_matrix); } ////////////////////////////////////////////////////////////////////////////////// // EXPOSURE FUNCTION // ////////////////////////////////////////////////////////////////////////////////// void ExposeFunc() { float aspect_ratio; char info_string[256]; ///////////////////////////////// // RESIZE VIEWPORT // ///////////////////////////////// XGetWindowAttributes(dpy, win, &wa); glViewport(0, 0, wa.width, wa.height); aspect_ratio = (float)(wa.width) / (float)(wa.height); ///////////////////////////////////////// // SETUP PROJECTION & MODELVIEW // ///////////////////////////////////////// glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-2.50*aspect_ratio, 2.50*aspect_ratio, -2.50, 2.50, 1., 100.); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(10., 0., 0., 0., 0., 0., 0., 0., 1.); glMultMatrixf(rotation_matrix); ///////////////////////////////// // DRAW CUBE // ///////////////////////////////// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); DrawCube(1.0); ///////////////////////////////// // DISPLAY TIME, FPS etc. // ///////////////////////////////// glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, (float)wa.width, 0, (float)wa.height, -1., 1.); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glColor3f(1.0, 1.0, 1.0); sprintf(info_string, "%4.1f seconds * %4.1f fps at %i x %i", TimeCounter, FPS, wa.width, wa.height, rot_z_vel); glRasterPos2i(10, 10); glCallLists(strlen(info_string), GL_UNSIGNED_BYTE, info_string); sprintf(info_string, "<up,down,left,right> rotate cube * <F1> stop rotation "); glRasterPos2i(10, wa.height-32); glCallLists(strlen(info_string), GL_UNSIGNED_BYTE, info_string); ///////////////////////////////// // SWAP BUFFERS // ///////////////////////////////// glXSwapBuffers(dpy, win); } ////////////////////////////////////////////////////////////////////////////////// // CREATE A GL CAPABLE WINDOW // ////////////////////////////////////////////////////////////////////////////////// void CreateWindow() { if((dpy = XOpenDisplay(NULL)) == NULL) { printf("\n\tcannot connect to x server\n\n"); exit(0); } root = DefaultRootWindow(dpy); if((vi = glXChooseVisual(dpy, 0, att)) == NULL) { printf("\n\tno matching visual\n\n"); exit(0); } if((cmap = XCreateColormap(dpy, root, vi->visual, AllocNone)) == 0) { printf("\n\tcannot create colormap\n\n"); exit(0); } swa.event_mask = KeyPressMask; swa.colormap = cmap; win = XCreateWindow(dpy, root, 0, 0, 700, 700, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa); XStoreName(dpy, win, "OpenGL Animation"); XMapWindow(dpy, win); } ////////////////////////////////////////////////////////////////////////////////// // SETUP GL CONTEXT // ////////////////////////////////////////////////////////////////////////////////// void SetupGL() { char font_string[128]; XFontStruct *font_struct; ///////////////////////////////////////////////// // CREATE GL CONTEXT AND MAKE IT CURRENT // ///////////////////////////////////////////////// if((glc = glXCreateContext(dpy, vi, NULL, GL_TRUE)) == NULL) { printf("\n\tcannot create gl context\n\n"); exit(0); } glXMakeCurrent(dpy, win, glc); glEnable(GL_DEPTH_TEST); glClearColor(0.00, 0.00, 0.40, 1.00); ///////////////////////////////////////////////// // FIND A FONT // ///////////////////////////////////////////////// for(int font_size = 14; font_size < 32; font_size += 2) { sprintf(font_string, "-adobe-courier-*-r-normal--%i-*", font_size); font_struct = XLoadQueryFont(dpy, font_string); if(font_struct != NULL) { glXUseXFont(font_struct->fid, 32, 192, 32); break; } } ///////////////////////////////////////////////// // INITIALIZE ROTATION MATRIX // ///////////////////////////////////////////////// glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glGetFloatv(GL_MODELVIEW_MATRIX, rotation_matrix); } ////////////////////////////////////////////////////////////////////////////////// // TIME COUNTER FUNCTIONS // ////////////////////////////////////////////////////////////////////////////////// void InitTimeCounter() { gettimeofday(&tv0, NULL); FramesPerFPS = 30; } void UpdateTimeCounter() { LastFrameTimeCounter = TimeCounter; gettimeofday(&tv, NULL); TimeCounter = (float)(tv.tv_sec-tv0.tv_sec) + 0.000001*((float)(tv.tv_usec-tv0.tv_usec)); DT = TimeCounter - LastFrameTimeCounter; } void CalculateFPS() { Frame ++; if((Frame%FramesPerFPS) == 0) { FPS = ((float)(FramesPerFPS)) / (TimeCounter-prevTime); prevTime = TimeCounter; } } ////////////////////////////////////////////////////////////////////////////////// // EXIT PROGRAM // ////////////////////////////////////////////////////////////////////////////////// void ExitProgram() { glXMakeCurrent(dpy, None, NULL); glXDestroyContext(dpy, glc); XDestroyWindow(dpy, win); XCloseDisplay(dpy); exit(0); } ////////////////////////////////////////////////////////////////////////////////// // CHECK EVENTS // ////////////////////////////////////////////////////////////////////////////////// void CheckKeyboard() { if(XCheckWindowEvent(dpy, win, KeyPressMask, &xev)) { char *key_string = XKeysymToString(XkbKeycodeToKeysym(dpy, xev.xkey.keycode, 0, 0)); if(strncmp(key_string, "Left", 4) == 0) { rot_z_vel -= 200.0*DT; } else if(strncmp(key_string, "Right", 5) == 0) { rot_z_vel += 200.0*DT; } else if(strncmp(key_string, "Up", 2) == 0) { rot_y_vel -= 200.0*DT; } else if(strncmp(key_string, "Down", 4) == 0) { rot_y_vel += 200.0*DT; } else if(strncmp(key_string, "F1", 2) == 0) { rot_y_vel = 0.0; rot_z_vel = 0.0; } else if(strncmp(key_string, "Escape", 5) == 0) { ExitProgram(); } } } ////////////////////////////////////////////////////////////////////////////////// // MAIN PROGRAM // ////////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]){ CreateWindow(); SetupGL(); InitTimeCounter(); while(true) { UpdateTimeCounter(); CalculateFPS(); RotateCube(); ExposeFunc(); usleep(1000); CheckKeyboard(); } }