A C++ Inheritance and Graphics Interoperability Example
With the 3.0 release, C++ inheritance is now supported for both classes and templates!
Inheritance is a fundamental concept in the C++ language. Without doubt, the CUDA 3.0 release greatly enhances the ability of C++ classes, templates, and libraries to efficiently use both GPU and CPU resources. While working with C++ on GPUs is a future topic, this article will provide working examples of the simplePBO code discussed in Part 15 of this article series. Far from a cosmetic change, C++ was purposefully utilized so inheritance could be demonstrated in deriving a new class that can render using arbitrary programmable shaders written in Cg.
Both C++ and Cg are extensive topics that are well beyond the scope of this article. However, working C++ and Cg source code along with the commands used to build the examples under Linux are provided. The C++ source code should be familiar as much of the code from Part 15 has been reused. Readers can refer to Part 15 for a more in-depth discussion about the code and APIs that implement the C++ methods.
Even those not interested in C++ or Cg will find it useful to scan the examples because the differences between the old and new graphics interoperability API are highlighted by the code contained in the #ifdef USE_CUDA3 preprocessor directives.
The following is the source code for mandelbrotPBO.cu. It is organized from the start of the example as follows:
- Header and preprocessor directives. Please remove the
#define USE_CUDA3to build the code on earlier releases. Note that the graphics interoperability APIs prior to the 3.0 release are deprecated and will go away in a future release. - An implementation of the Mandelbrot kernel that calculates the data to be displayed. Obviously this kernel can be replaced to run other calculations on the GPU. Perhaps some of the more adventurous readers might like to try using the Perlin noise generator discussed in Part 15.
- A C++ functor that calls the GPU kernel. A functor is basically a function that preserves state information.
- The definition, variables, and methods of the
drawable2DTextureclass. Most of the methods in this class were discussed in Part 15. Please note that the C++ protected keyword was used instead of private for inheritance reasons. - Several callback functions for GLUT. Further discussion can be found in Part 15. For simplicity, the
cleanupCallback()was left unspecified. - The main program.
The complete mandelbrotPBO.cu source code is as follows:
#include <GL/glew.h>
#include <GL/glut.h>
#define USE_CUDA3
#include <cuda.h>
#include <cuda_gl_interop.h>
__global__ void mandelbrot( uchar4 *d_ptr, ulong2 size,
float2 rrange, float2 irange, unsigned long n )
{
unsigned long x = blockIdx.x * blockDim.x + threadIdx.x;
unsigned long y = blockIdx.y * blockDim.y + threadIdx.y;
if ( x >= size.x || y >= size.y ) return;
float2 C = make_float2( ( rrange.y - rrange.x ) / size.x * x + rrange.x,
( irange.y - irange.x ) / size.y * y + irange.x );
uchar4 buf = make_uchar4( 0, 0, 0, 0 );
if ( C.x * C.x + C.y * C.y <= 4.0f ) {
float2 z = make_float2( 0.0f, 0.0f );
unsigned long cnt = 0;
while ( cnt < n ) {
++cnt;
z = make_float2( z.x * z.x - z.y * z.y + C.x, 2.0f * z.x * z.y + C.y );
if ( z.x * z.x + z.y * z.y > 4.0f ) {
unsigned char c = ( 255 - 15 ) * __powf( ( float )cnt / n, 0.8f )+15;
buf = make_uchar4( c, c, c, 0 );
break;
}
}
}
d_ptr[ x + y * size.x ] = buf;
}
// A functor is basically a function that maintains state
struct gpuFunctor {
protected:
float2 rrange;
float2 irange;
public:
gpuFunctor(float2 rrange, float2 irange) : rrange(rrange), irange(irange) {}
gpuFunctor() : rrange(make_float2(-2.f, 2.f)), irange(make_float2(-2.f, 2.f)) {}
void callKernel(uchar4 *d_ptr, ulong2 gridSize, unsigned long convergence)
{
dim3 dimBlock( 16, 4 );
dim3 dimGrid( ( gridSize.x + dimBlock.x - 1 ) / dimBlock.x,
( gridSize.y + dimBlock.y - 1 ) / dimBlock.y );
mandelbrot<<< dimGrid, dimBlock >>>( d_ptr, gridSize, rrange,
irange, convergence );
}
inline float2 getRrange() {return(rrange);}
inline float2 getIrange() {return(irange);}
};
class drawable2DTexture {
protected:
GLuint tex,pbo;
ulong2 size;
gpuFunctor gpuFunc;
#ifdef USE_CUDA3
struct cudaGraphicsResource *pboCUDA;
#endif
void createPBO()
{
// set up vertex data parameter
int num_texels = size.x * size.y;
int num_values = num_texels * 4;
int size_tex_data = sizeof(GLubyte) * num_values;
// Generate a buffer ID called a PBO (Pixel Buffer Object)
glGenBuffers(1,&pbo);
// Make this the current UNPACK buffer (OpenGL is state-based)
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
// Allocate data for the buffer. 4-channel 8-bit image
glBufferData(GL_PIXEL_UNPACK_BUFFER, size_tex_data, NULL, GL_DYNAMIC_COPY);
#ifdef USE_CUDA3
cudaGraphicsGLRegisterBuffer( &pboCUDA, pbo, cudaGraphicsMapFlagsNone );
#else
cudaGLRegisterBufferObject( pbo );
#endif
}
void deletePBO()
{
if(pbo) {
// delete the PBO
#ifdef USE_CUDA3
cudaGraphicsUnregisterResource( pboCUDA );
#else
cudaGLUnregisterBufferObject( pbo );
#endif
glDeleteBuffers( 1, &pbo );
pbo=NULL;
pboCUDA = NULL;
}
}
void createTexture()
{
if(tex) deleteTexture();
unsigned int image_height=size.y;
unsigned int image_width=size.x;
// Enable Texturing
glEnable(GL_TEXTURE_2D);
// Generate a texture identifier
glGenTextures(1,&tex);
// Make this the current texture (remember that GL is state-based)
glBindTexture( GL_TEXTURE_2D, tex);
// Allocate the texture memory. The last parameter is NULL since we only
// want to allocate memory, not initialize it
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, image_width, image_height, 0, GL_BGRA,
GL_UNSIGNED_BYTE, NULL);
// Must set the filter mode, GL_LINEAR enables interpolation when scaling
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// Note: GL_TEXTURE_RECTANGLE_ARB may be used instead of
// GL_TEXTURE_2D for improved performance if linear interpolation is
// not desired. Replace GL_LINEAR with GL_NEAREST in the
// glTexParameteri() call
}
void deleteTexture()
{
if(tex) {
glDeleteTextures(1, &tex);
tex = NULL;
}
}
void cleanup (void) { deletePBO(); deleteTexture(); }
public:
~drawable2DTexture() { cleanup(); }
drawable2DTexture(ulong2 winSize, gpuFunctor userGPUfunc)
{
size = winSize;
gpuFunc = userGPUfunc;
#ifdef USE_CUDA3
pboCUDA = NULL;
#endif
createTexture();
createPBO();
}
void draw( unsigned long convergence )
{
uchar4 *d_ptr = NULL;
#ifdef USE_CUDA3
size_t start;
cudaGraphicsMapResources( 1, &pboCUDA, NULL );
cudaGraphicsResourceGetMappedPointer( ( void ** )&d_ptr, &start, pboCUDA );
#else
cudaGLMapBufferObject( ( void ** )&d_ptr, pbo );
#endif
gpuFunc.callKernel(d_ptr, size, convergence);
cudaThreadSynchronize();
#ifdef USE_CUDA3
cudaGraphicsUnmapResources( 1, &pboCUDA, NULL );
#else
cudaGLUnmapBufferObject( pbo );
#endif
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBindTexture( GL_TEXTURE_2D, tex );
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, size.x, size.y,
GL_RGBA, GL_UNSIGNED_BYTE, NULL );
}
void display( void )
{
glBegin( GL_QUADS );
glTexCoord2f( 0.0f, 0.0f ); glVertex2f( 0.0f, 0.0f );
glTexCoord2f( 1.0f, 0.0f ); glVertex2f( 1.0f, 0.0f );
glTexCoord2f( 1.0f, 1.0f ); glVertex2f( 1.0f, 1.0f );
glTexCoord2f( 0.0f, 1.0f ); glVertex2f( 0.0f, 1.0f );
glEnd();
}
void reshape(ulong2 size)
{
float2 irange = gpuFunc.getIrange();
float2 rrange = gpuFunc.getRrange();
float icenter = ( irange.x + irange.y ) * 0.5f;
float iwidth = ( rrange.y - rrange.x ) / size.x * size.y * 0.5f;
irange = make_float2( icenter - iwidth, icenter + iwidth );
gpuFunc = gpuFunctor(rrange, irange);
}
};
drawable2DTexture *imageTexture=NULL;
void reshapeCallback( int w, int h )
{
glViewport( 0, 0, w, h );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f );
imageTexture->reshape( make_ulong2( h, w ) );
}
void displayCallback( void )
{
imageTexture->draw( 1024 );
imageTexture->display();
glutSwapBuffers();
}
void cleanupCallback( void)
{
}
int main(int argc, char *argv[] )
{
int height=800, width = 600;
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
glutInitWindowSize( height, width);
glutCreateWindow( *argv );
glewInit();
glutDisplayFunc( displayCallback );
glutReshapeFunc( reshapeCallback );
cudaGLSetGLDevice( 0 );
// create the pixel image object
imageTexture = new drawable2DTexture(make_ulong2(height,width), gpuFunctor());
atexit( cleanupCallback );
glutMainLoop();
cleanupCallback();
cudaThreadExit();
return 0;
}
The executable can be built under Linux with the following nvcc command-line script:
SDK_PATH=$HOME/NVIDIA_GPU_Computing_SDK/C INC=$SDK_PATH/common/inc LIB=$SDK_PATH/lib nvcc -O3 -I$INC -L$LIB mandelbrotPBO.cu -lcutil_x86_64 -lglut -lGLEW -o mandelbrotPBO


