Vertex Buffer Objects
Most of the PBO software framework described in Part 15 will be reused to demonstrate drawing with VBOs in this article. As with pixel buffer objects, high-speed interoperability is achieved by mapping OpenGL buffers into the CUDA memory space without requiring a memory copy. Detailed information about the API and OpenGL calls used when mixing CUDA with OpenGL can be found in the PBO article as well as Joe Stam's 2009 NVIDIA GTC conference presentation What Every CUDA Programmer Should Know About OpenGL (also available in video here).
Just as in the NVIDIA SDK samples, GLUT (a window system independent OpenGL Toolkit) was utilized for Windows and Linux compatibility. This article focuses on building and running the examples under Linux. Go here for more information about building these examples with Microsoft Visual Studio.
Figure 8 summarizes how the VBO example code interacts with GLUT. The boxes highlighted in green indicate those areas that required refactoring the original PBO code so it can work with VBOs.
The relationship between the four files used in the procedural framework discussed in this article is illustrated in Figure 9.
The simpleGLmain.cpp File
The file simplGLmain.cpp from Part 15 already provides all the boilerplate needed to open a window on the screen and set some basic viewing transforms sufficient to display the two-dimensional pictures created in our PBO examples.
From a coding point of view, adapting the 2D PBO version of simpleGLmain.cpp to display 3D VBO objects merely requires specifying a three-dimensional perspective in initGL with the call:
gluPerspective(60.0, (GLfloat)window_width/(GLfloat)window_height, 0.1, 1, 0.);
From an OpenGL point of view, adding a perspective with gluPerspective effectively places a camera in a three-dimensional location from which to view the data generated by our CUDA kernel(s). Three-dimensional rendering occurs on the GPU when the programmer:
- Specifies objects in 3D space using simple triangles, vertices and lines.
- Defines a virtual camera position and viewing angle.
The GPU can then identify and update the display pixels as the data and/or viewing position changes.
Continuing with the camera analogy, the required transforms for 3D data are:
- Position and point the camera at the scene (a view transformation)
- Arrange the scene composition (a model transform)
- Adjust the camera zoom (a projection transform)
- Choose the final size (a viewport transform)
OpenGL transforms and coordinate systems require very detailed thinking and explanation. Suffice it to say that defining the projection transform is all that was needed to adapt simplePBO.cpp so 3D VBO data can be displayed.
Song Ho Ann has an excellent set of tutorials and visual aids to help understand the details of OpenGL transforms, the OpenGL rendering pipeline (including differences between pixel and geometry rendering), the OpenGL projection matrix, and much more. I recommend these tutorials or one of the many other excellent resources available on the web including the online version of Chapter 3 of the OpenGL Red Book.
The following is the complete source code for the VBO version of simpleGLmain.cpp.
// simpleGLmain.cpp (Rob Farber)
/*
This wrapper demonstrates how to use the Cuda OpenGL bindings to
dynamically modify data using a Cuda kernel and display it with opengl.
*/
// includes, GL
#include <GL/glew.h>
// includes
#include <cuda_runtime.h>
#include <cutil_inline.h>
#include <cutil_gl_inline.h>
#include <cutil_gl_error.h>
#include <cuda_gl_interop.h>
#include <rendercheck_gl.h>
// The user must create the following routines:
void initCuda(int argc, char** argv);
// GLUT specific variables
const unsigned int window_width = 512;
const unsigned int window_height = 512;
unsigned int timer = 0; // a timer for FPS calculations
// Forward declaration of GL functionality
CUTBoolean initGL(int argc, char** argv);
// Rendering callbacks
void fpsDisplay(), display();
void keyboard(unsigned char key, int x, int y);
void mouse(int button, int state, int x, int y);
void motion(int x, int y);
// Main program
int main(int argc, char** argv)
{
// Create the CUTIL timer
cutilCheckError( cutCreateTimer( &timer));
if (CUTFalse == initGL(argc, argv)) {
return CUTFalse;
}
initCuda(argc, argv);
CUT_CHECK_ERROR_GL();
// register callbacks
glutDisplayFunc(fpsDisplay);
glutKeyboardFunc(keyboard);
glutMouseFunc(mouse);
glutMotionFunc(motion);
// start rendering mainloop
glutMainLoop();
// clean up
cudaThreadExit();
cutilExit(argc, argv);
}
// Simple method to display the Frames Per Second in the window title
void computeFPS()
{
static int fpsCount=0;
static int fpsLimit=100;
fpsCount++;
if (fpsCount == fpsLimit) {
char fps[256];
float ifps = 1.f / (cutGetAverageTimerValue(timer) / 1000.f);
sprintf(fps, "Cuda GL Interop Wrapper: %3.1f fps ", ifps);
glutSetWindowTitle(fps);
fpsCount = 0;
cutilCheckError(cutResetTimer(timer));
}
}
void fpsDisplay()
{
cutilCheckError(cutStartTimer(timer));
display();
cutilCheckError(cutStopTimer(timer));
computeFPS();
}
float animTime = 0.0; // time the animation has been running
// Initialize OpenGL window
CUTBoolean initGL(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowSize(window_width, window_height);
glutCreateWindow("Cuda GL Interop Demo (adapted from NVIDIA's simpleGL");
glutDisplayFunc(fpsDisplay);
glutKeyboardFunc(keyboard);
glutMotionFunc(motion);
// initialize necessary OpenGL extensions
glewInit();
if (! glewIsSupported("GL_VERSION_2_0 ")) {
fprintf(stderr, "ERROR: Support for necessary OpenGL extensions missing.");
fflush(stderr);
return CUTFalse;
}
// default initialization
glClearColor(0.0, 0.0, 0.0, 1.0);
glDisable(GL_DEPTH_TEST);
// viewport
glViewport(0, 0, window_width, window_height);
// set view matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// projection
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)window_width / (GLfloat) window_height,
0.10, 10.0);
return CUTTrue;
}


