![]() |
are you ready for a mindfuck? | |||||||||||||||||||||
|
....::::Menu::::.... ---------------------------...::About::... ...::Articles::... ...::Contact::... ...::Home & News::... ...::Links & Credits::... --------------------------- --------------------------- |
Introduction to vertex shaders using CGby Elie De BrauwerGoal of this fileThe 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
CgThis 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. 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.
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 ShaderNow 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:
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:
Next we uncomment those two lines and load the shader. The results looks ... green. This means the shaders works !.
A note on compilingThe 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.
|