are you ready for a mindfuck ? are you ready for a mindfuck?

....::::Menu::::....
...::About::...
...::Articles::...
...::Contact::...
...::Home & News::...
...::Links & Credits::... OpenGL logo
Valid XHTML 1.0!

More vertex programs using CG

by Elie De Brauwer

Goal of this file

The goal of this file is to extend the introduction on how to use vertex programs writting in Cg (C for graphics see: http://developer.nvidia.com/Cg) under the Linux operation system. This files extends the introduction with: freeing resources, handling errors and using parameters.

History of this file

  • 24 june 2004: created

Handling Cg errors

Nothing is more confusing than putting your time in something and after a while find out that things aren't working like you expected them to work out. At such a moment in a conventional programming language you'd use for example the errno variable or use a debugger. With OpenGL and Cg things are a little different. Either it works or it doesn't work and locating the error is sometimes difficult. For a discussion on how to handle OpenGL errors you should look at a previous article.
That covers the OpenGL part but what when something went wrong with Cg ? And a lot of things can go wrong. You can specify a non existent cg source file, you can try to use a wrong profile on a wrong video card without support for that profile. You can pass a wrong entry point along with the Cg program, and numerous silly mistakes that you will all make one time or another. In that case it's nice to have a function to spit out errors that occured.
To accomplish this Cg provides the programmer with an error callback. This callback can be set with void cgSetErrorCallBack( CGerrorCallBackFunc func );, this function takes a function with prototype void func(void) as a parameter and this function will be called each time an error occurs.
To identify the error we have int cgGetError(); which returns the error number or zero if no error is occured. In combination with const char * cgGetErrorString( int error ); we can ask Cg to return a human readable error string where the error parameter passed is the number retrieved from cgGetError().
For example when I alter entru function parameter in the cgCreateProgramFromFile() call from main to maink (which is nonexistent) and I run the program and use the following function as an error callback:

void cgErrorCb(){
  printf("%s\n", cgGetErrorString(cgGetError()));
}

A result is returned stating that the compilation has failed.

helios@qntal:~/workdir/cg$ ./shadertest 
CG ERROR : The compile returned an error.

Setting parameters

Presented the following vertex program:

struct output{
  float4 position : POSITION;
  float4 color : COLOR;
};

// Uniform says the cg compiler that the constantColor variable is 
// defined elsewhere.
output main(float2 position: POSITION, uniform float4 constantColor){
  output OUT;
  OUT.position = float4(position, 0, 1);
  OUT.color = constantColor;
  return OUT;
}

You can see some differences with the shader (which is in the archive that can be downloaded). Mainly there are two big differences the first one is that the main function takes a second float4 constantColor parameter and that the output color isn't hard coded to green anymore but that it uses the constantColor as the output color. This means that when we have the power to change the constantColor from our program we can let the vertex program output that color (basicly doing the same as the glColor4f() call).
The uniform keyword is used to tell the Cg compiler that the constantColor parameter will be set by the calling environment (that's us, the OpenGL programmer).
We can use CGparameter cgGetNamedParameter( CGprogram prog, const char * name ); to get the named parameter defined by name in the program prog. Another function that might come in handy is CGbool cgIsParameter( CGparameter param ); to determine if a CGParameter references a valid parameter. Now that we have the parameter we still have to set it which might be a problem since C/C++ doesn't know the float4 type that Cg is using. This is why the Cg api provides the programmer with a whole list of cgGLSetParameter?() functions calls. Below a pice from the cgGLSetParameter man page:

         void cgGLSetParameter1{fd}(CGparameter param,
                                    type x);
         void cgGLSetParameter2{fd}(CGparameter param,
                                    type x,
                                    type y);
         void cgGLSetParameter3{fd}(CGparameter param,
                                    type x,
                                    type y,
                                    type z);
         void cgGLSetParameter4{fd}(CGparameter param,
                                    type x,
                                    type y,
                                    type z,
                                    type w);
         void cgGLSetParameter{1234}{fd}v(CGparameter param,
                                          const type *v);

Cleaning up

What do you do after you did a malloc ? What do you do after you did a new ? Indeed you have to free the resources and give them back to the system. Within the Gc api you should also give the resources back. If you want to give the resources for a single program back you should use void cgDestroyProgram( CGprogram prog ); or if you want to give the resources used by an entire CGcontext you should use: void cgDestroyContext( CGcontext ctx );

Putting it all together

To illustrate the three subjects discussed above the example program from the introduction to shader programs article was used. The modified shader program is already printed above.

#include <Cg/cg.h>                 // Including Cg runtime API
#include <Cg/cgGL.h>               // Including OpenGL-specific Cg runtime API
#include <GL/glut.h>               // Glut and OpenGL api
#include <stdlib.h>
#include <unistd.h>                // sleep

void disp();
void keyb(unsigned char k, int x, int y);
void cgErrorCb();

CGcontext context;
CGprogram program;
CGparameter param;

float redvalue; 
float delta; 

The prototype for the error callback function were added and some global variables were added so that we are able to access some variables from the callback functions. redvalue is the amount of red the triangle will have and delta is the difference between the current and the next redvalue.

int main(int argc, char **argv){
  glutInit(&argc, argv);
  glutInitWindowPosition(0,0);
  glutInitWindowSize(640,480);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
  glutCreateWindow("Cg test");
  glutDisplayFunc(disp);
  glutKeyboardFunc(keyb);

  redvalue=0;
  delta=0.01;

  // Register the cg error Callback
  cgSetErrorCallback(cgErrorCb);
  // Create the context, this is a container for Cg programs
  context = cgCreateContext();
  if(context == NULL){
    printf("Failed to create CGcontext\n");
    exit(-1);
  }else{
     program = cgCreateProgramFromFile(context,  // The context where we want it added
                                        CG_SOURCE,        // The following parameter is an array 
					                  // of bytes containing Cg source
                                        "./shader.cg",    // The sourcefile
                                        CG_PROFILE_ARBVP1,// The profile 
                                        "main",           // The entry function 
                                        NULL              // Compiler options
                                        );
    if(program==NULL){
      printf("Failed to compile the program\n");
      exit(-1);
    }else{
      // Now load the program
      cgGLLoadProgram(program);
      
      // Enable the profile
      cgGLEnableProfile(CG_PROFILE_ARBVP1);

      // Get the parameter 
      param = cgGetNamedParameter(program,"constantColor");
      if(param == NULL){
        printf("Could not get the parameter\n");
        exit(0);
      }
      // Bind the program
      cgGLBindProgram(program);
    }
  }

  glClearColor(0.0,0.0,0.0,0.0);
  glutMainLoop();
}


void disp(){
  // Set the vertex program parameter
  cgGLSetParameter4f(param,redvalue,0,0,1);
  redvalue+=delta;
  if(redvalue>=1){
    delta*=-1;
    redvalue=1.0;
  }else if(redvalue<=0){
    delta*=-1;
    redvalue=0.0;
  }
  redvalue+=delta;

  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_TRIANGLES);
  glVertex2f(-0.8, 0.8);
  glVertex2f( 0.8, 0.8);
  glVertex2f( 0.0,-0.8);
  glEnd();
  glutSwapBuffers();
  sleep(1);
  glutPostRedisplay();
}

In the main function we created a reference to the parameter and this parameter is changed in the disp() function. We also assure the redvalue is between in the interval [0,1] and that delta is changed so that we first go from black to red and then from red to black and so on...
After the display function did what it should do we sleep and we post a redisplay. So it won't get to flashy on newer cards.


void keyb(unsigned char k, int x, int y){
  if(k=='q'){
    // Free resources 
    cgDestroyProgram(program);
    exit(0);
  }
}

void cgErrorCb(){
  printf("%s\n", cgGetErrorString(cgGetError()));
}

Last but not least we free the resources when the user pressed 'q' and the content of the error callback.

Last words

The sources for this article are available here. No screenshots for this article but I thing your imagination can came up with a red triangle.

Cg logo