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!

Introduction to vertex shaders using CG

by Elie De Brauwer

Goal of this file

The goal of this file is to give an introduction on how to use vertex shaders writting in Cg (C for graphics see: http://developer.nvidia.com/Cg) under the Linux operation system. This file covers the bare essentials on how getting a Vertex shader to work.

History of this file

  • 23 june 2004: created
  • 24 june 2004: little addition

Cg

This site focuses on OpenGL and not on Cg so I won't go into detail on Cg itself. For more information about Cg you should visit:

But in short, a graphics card (most modern graphics cards) contain a GPU (graphical procession unit, a CPU but focused on graphical work). These GPU's are programmable, until some time ago they were only programmable in an assembler alike language. For example:

	def c1, 0, 1, 0, 1
	def c0, 0, 1, 0, 0
	mov oPos.xy, v0.xyyy
        mov oPos.zw, c0.yyxy
        mov oD0, c1.xyxy

The code above is the assembler version that can be linked to binary code understandable for the GPU, but like we all know assembler isn't that easy to read/learn/maintain so Nvidia came up with Cg (C for Graphics). A language with a C/C++ look alike syntax which can be used to create software specifically for the GPU. Which run on the GPU and alter the way stages in the graphics programming pipeline work. Within the programmable graphics pipeline two elements can be programmed: the programmable vertex processor and the programmable fragment processor.
For more information on Cg I suggest The Cg Tutorial, The Definitive Guide to Programmable Real-Time Graphics by Randima Fernando and Mark J. Kilgard (the GLUT author). The book homepage can be found here.

Getting started

So far I've said Cg is a programming language and it needs to be compiled and linked to the architecture of your GPU. As you might suspected gcc doesn't support this (yet ?). But Nvidia (and no I don't get any money or hardware each time I mention Nvidia :-( ) has a SDK (software development kit) and a CG compiler you can get from there homepage. This can be downloaded from: http://developer.nvidia.com/object/cg_toolkit.html. You certainly need the compiler (which can be converted to .deb using Alien and installed on a Debian box for example). Currently download are available for Windows, Linux and Mac OS X. You certainly need the compiler which offers you the compiler and manpages.
If you want to verify you installed the compiler correctly run:

helios@qntal:~/workdir/cg$ cgc shader.cg
shader.cg
11 lines, 0 errors.
vs.1.1
// DX8 Vertex shader generated by NVIDIA Cg compiler
// cgc version 1.2.1001, build date Mar 17 2004  10:58:07
// nv30vp backend compiling 'main' program
def c1, 0, 1, 0, 1
def c0, 0, 1, 0, 0
//vendor NVIDIA Corporation
//version 1.0.02
//profile vs_1_1
//program main
//var float2 position : $vin.POSITION : ATTR0 : 0 : 1
//var float4 main.position : $vout.POSITION : HPOS : -1 : 1
//var float4 main.color : $vout.COLOR : COL0 : -1 : 1
//const c[1] = 0 1 0 1
//const c[0] = 0 1 0 0
        mov oPos.xy, v0.xyyy
        mov oPos.zw, c0.yyxy
        mov oD0, c1.xyxy
// 3 instructions
// 0 temp registers
// End of program

Where shader.cg is the shader provided with the sample code from this article which you can get here. When you observer the output you can see that the assembler code is the same as the one used above.

The Shader

Now lets take a look at the contents of shader.gc:

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

output main(float2 position: POSITION){
  output OUT;
  OUT.position = float4(position, 0, 1);
  OUT.color = float4(0,1,0,1);
  return OUT;
}

Now this shader is almost C/C++. It defines a struct containing 2 float4 (float4 is something like and array but not quite like an array). We have a main function returning the output structure. The position isn't altered but the color gets set to green. So what does this vertex shader do ? It changes the colors to green that's it. This example is also the first example mentioned in the Cg tutorial.

Cg meets OpenGL

"Hey, you stole the code from the book, you just took the first chapter and put you name under it!". Yes and no, I used the shader but I'm using the shader as a piece of code that needs to get implemented in OpenGL, the CG tutorial is a CG tutorial and not a using CG and OpenGL tutorial. So the main goal of this article is showing how to implement a given shader. Below you can find the sourcecode of shadertest.c.

#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>


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

Nothing exciting here, some includes and callback prototypes. We have to include Cg/cg.h for the general Cg API and Cg/cgGL.h for the OpenGL specific data.

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);

  // Create the context, this is a container for Cg programs
  CGcontext context = cgCreateContext();
  if(context == NULL){
    printf("Failed to create CGcontext\n");
    exit(-1);
  }else{
    CGprogram 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);

      // Bind the program
      cgGLBindProgram(program);
    }
  }

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

First we start with defining the window like we always do then we create a CGcontext with CGcontext cgCreateContext();. This CGcontext is a container for CG applications, all CG applications should be added to a container like this. If we succeeded to create the container we try to load the program (this is the shader sourcecode). CGprogram cgCreateProgramFromFile(CGcontext ctx,CGenum program_type,const char *program_file, CGprofile profile, const char *entry, const char **args). Here is ctx the CGcontext we want to add the program into. The program_type is either CG_SOURCE or CG_OBJECT when the next parameter contains sourcecode or object code. Like I said the next parameter is the file containing either the CG sourcecode or the objectcode. Next comes the profile. If you write advanced vertex programs you should use advanced profiles but not many cards will support that profile (and your application). Here we use the lowest possible profile which corresponds to the ARB_vertex_program functionality. Other option are basic Nvidia vertex programmability (NV_vertex_program) or Advanced Nvidia vertex programmability (NV_vertex_program2). By looking at the glxinfo output you can find out what is supported by your card. For example:

helios@qntal:~/workdir/cg$ glxinfo | grep program
    GL_ARB_depth_texture, GL_ARB_fragment_program, GL_ARB_imaging, 
    GL_ARB_vertex_buffer_object, GL_ARB_vertex_program, GL_ARB_window_pos, 
    GL_NV_float_buffer, GL_NV_fog_distance, GL_NV_fragment_program, 
    GL_NV_vertex_array_range, GL_NV_vertex_array_range2, GL_NV_vertex_program, 
    GL_NV_vertex_program1_1, GL_NV_vertex_program2, GL_NVX_ycrcb, 

The following profiles are available for OpenGL:

NameTypeDescription
CG_PROFILE_ARBVP1VertexOpenGL ARB Vertex program
CG_PROFILE_VP30VertexOpenGL NV30 Vertex program
CG_PROFILE_VP20VertexOpenGL NV2X Fragment program
CG_PROFILE_ARBFP1FragmentOpenGL ARB Fragment program
CG_PROFILE_FP30FragmentOpenGL NV30 Fragment program
CG_PROFILE_FP20FragmentOpenGL NV2X Fragment program

So by defining which profile to use you tell your program if you are using a vertex program or a fragment program. In the Cg tutorial this vertex program is used with a passthrough fragment program. But to illustrate the loading process we don't require to load the fragment program.

To get an overview of the profiles support by your Cg compiler just ask it:

helios@qntal:/usr/include/Cg$ cgc -help
(0) : fatal error C9999: bad arguments

usage: cgc [-quiet] [-nocode] [-nostdlib] [-[no]fx] [-longprogs] [-v] [-strict]
           [-Dmacro[=value]] [-Iinclude_dir] [-profile id] [-entry id]
           [-profileopts opt1,opt2,...] [-o ofile] [-l lfile] [-[no]fastmath]
           [-maxunrollcount N] [-type <type definition>} [-typefile <file>}
           {file.cg}
supported profiles and their supported profileopts:
    arbfp1    profileopts:
        NumTemps=<val>
        NumInstructionSlots=<val>
        NoDependentReadLimit=<val>
        NumTexInstructionSlots=<val>
        NumMathInstructionSlots=<val>
        MaxTexIndirections=<val>
    ps_2_x    profileopts:
        NumTemps=<val>
        NumInstructionSlots=<val>
        Predication=<val>
        ArbitrarySwizzle=<val>
        GradientInstructions=<val>
        NoDependentReadLimit=<val>
        NoTexInstructionLimit=<val>
    ps_2_0    profileopts:
    dx9ps2    profileopts:
    fp30      profileopts:
        NumInstructionSlots=<val>
    vs_2_x    profileopts:
        DynamicFlowControlDepth=<0 or 24>
        NumTemps=<12 to 32>
        Predication=<0 or 1>
    vs_2_0    profileopts:
    dxvs2     profileopts:
    arbvp1    profileopts:
        NumTemps=<12 to 32>
        MaxAddressRegs=<1 or 2>
        MaxInstructions=<n>
        MaxLocalParams=<n>
    vs_1_1    profileopts:
        dcls
    dx8vs     profileopts:
        dcls
    vp20      profileopts:
    vp30      profileopts:
    ps_1_3    profileopts:
        MaxPixelShaderValue=<val>
    ps_1_2    profileopts:
        MaxPixelShaderValue=<val>
    ps_1_1    profileopts:
        MaxPixelShaderValue=<val>
    dx8ps     profileopts:
        MaxPixelShaderValue=<val>
    fp20      profileopts:
    generic   profileopts:

It is suggested starting with CG_PROFILE_ARBVP1 so that most cards will support your application.. The next parameter is the entry fuction. This is the main function of your shader. If this main function is called main this may be omitted (insert NULL) but it is more clear when we enter it. But Cg does not obligate you to call your main 'main'. It could be called 'shader' for example. The last parameter contains arguments that needs to be passed to the CG compiler. If none are needed you pass NULL. After that you check if you program loads and we continue to load the program void cgGLLoadProgram(CGprogram prog);, enable the profile void cgGLEnableProfile(CGprofile profile); and we bind the program void cgGLBindProgram(CGprogram prog);. At this point the shader is loaded and the GPU is programmed.


void disp(){
  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();
}


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

Now what we have here ? We have a display function that creates a triangle. Now, what color do you expect it to be without the shader enabled ? Indeed it should be white. First we verify this and we comment the line saying: "cgGLBindProgram(program);" where we load the program and "cgGLEnableProfile(CG_PROFILE_ARBVP1);" where we enable the profile. No we run the program and see the triangle is white:

A white triangle

Next we uncomment those two lines and load the shader. The results looks ... green. This means the shaders works !.

A green triangle

A note on compiling

The command used to compile this example was:

helios@qntal:~/workdir/cg$ gcc -o shadertest -lCg -lCgGL -lglut -lpthread shadertest.c

Now what are pthreads (POSIX Threads) doing there ? On my system they have to be there. When I omit them /usr/lib/libCg.so complains about and undefined symbol.

helios@qntal:~/workdir/cg$ ./shadertest 
./shadertest: relocation error: /usr/lib/libCg.so: undefined symbol: pthread_once

The solution for this problem is linking it with pthread. I don't know if this is a problem on other systems. I have only tested it on Debian but this seems to fix it. The sources for this article are available here.

Cg logo