Channels ▼
RSS

Tools

CUDA, Supercomputing for the Masses: Part 17


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:

  1. Header and preprocessor directives. Please remove the #define USE_CUDA3 to 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.
  2. 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.
  3. A C++ functor that calls the GPU kernel. A functor is basically a function that preserves state information.
  4. The definition, variables, and methods of the drawable2DTexture class. 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.
  5. Several callback functions for GLUT. Further discussion can be found in Part 15. For simplicity, the cleanupCallback() was left unspecified.
  6. 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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video