| GPU GLSL Geometry Shader |
| Lets get started! GLSL is the OpenGL shader language. If we do not have it up already, we will have a presentation regarding GLSL and programming for that environment soon!
We are using a NVIDIA 8800. Shader Model 4 defines a new shader type, the geometry shader. You can read about this unit by searching google for: Unified Shader Architecture, DirectX 10, and-or Shader Model 4. Bottom line, when you render, we specify vertices, which are processed by the vertex processor, then a primitive is formed and processed in the geometry shader, then the units are broken up and resterized, which are then processed by fragment units. You need to install a few things right now:
Lets get into the code. First we need to include a few libraries. | |
#include <stdio.h> //C standard IO
#include <stdlib.h> //C standard lib
#include <string.h> //C string lib
#include <GL/glew.h> //GLEW lib
#include <GL/glut.h> //GLUT lib
| The next two functions read in and write out a file. We will need them to load the GLSL shader code from file. | |
//Function from: http://www.evl.uic.edu/aej/594/code/ogl.cpp
//Read in a textfile (GLSL program)
// we need to pass it as a string to the GLSL driver
char *textFileRead(char *fn)
{
FILE *fp;
char *content = NULL;
int count=0;
if (fn != NULL) {
fp = fopen(fn,"rt");
if (fp != NULL) {
fseek(fp, 0, SEEK_END);
count = ftell(fp);
rewind(fp);
if (count > 0) {
content = (char *)malloc(sizeof(char) * (count+1));
count = fread(content,sizeof(char),count,fp);
content[count] = '/0';
}
fclose(fp);
}
}
return content;
}
//Function from: http://www.evl.uic.edu/aej/594/code/ogl.cpp
//Read in a textfile (GLSL program)
// we can use this to write to a text file
int textFileWrite(char *fn, char *s)
{
FILE *fp;
int status = 0;
if (fn != NULL) {
fp = fopen(fn,"w");
if (fp != NULL) {
if (fwrite(s,sizeof(char),strlen(s),fp) == strlen(s))
status = 1;
fclose(fp);
}
}
return(status);
}
| The next two functions can be used to pull back info about the GLSL programs. We can use this to check for errors. | |
//Got this from http://www.lighthouse3d.com/opengl/glsl/index.php?oglinfo
// it prints out shader info (debugging!)
void printShaderInfoLog(GLuint obj)
{
int infologLength = 0;
int charsWritten = 0;
char *infoLog;
glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
if (infologLength > 0)
{
infoLog = (char *)malloc(infologLength);
glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
printf("printShaderInfoLog: %s/n",infoLog);
free(infoLog);
}else{
printf("Shader Info Log: OK/n");
}
}
//Got this from http://www.lighthouse3d.com/opengl/glsl/index.php?oglinfo
// it prints out shader info (debugging!)
void printProgramInfoLog(GLuint obj)
{
int infologLength = 0;
int charsWritten = 0;
char *infoLog;
glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
if (infologLength > 0)
{
infoLog = (char *)malloc(infologLength);
glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
printf("printProgramInfoLog: %s/n",infoLog);
free(infoLog);
}else{
printf("Program Info Log: OK/n");
}
}
| Here are a few global variables. Basically, we use them as handles to the GPU programs. We also keep two integers that represent the screen width and height. | |
//YEAH, YEAH, YEAH ... global vars, this is a freggn demo!
GLuint v,f,f2,p,g; //Handlers for our vertex, geometry, and fragment shaders
int gw,gh; //Keep track of window width and height
| The next function sets up the viewport and the projection area. We setup the viewport to the screen size, and make the ortho region the same size as the screen width and height. | |
//GLUT callback fx
// called when window size changes
void changeSize(int w, int h) {
//stores the width and height
gw = w;
gh = h;
//Set the viewport (fills the entire window)
glViewport(0,0,gw,gh);
//Go to projection mode and load the identity
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//Orthographic projection, stretched to fit the screen dimensions
gluOrtho2D(0,gw,0,gh);
//Go back to modelview and load the identity
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
| The next section of code renders a single line. We use the geometry shader to create a new line within the GPU (i.e. it is not issued from the host, but created on the GPU!). In the new line we switch the x and y positions of the line passed down. This will create a cross on the screen (really simple application here!). Nothing really changes on the CPU side! | |
void renderScene(void)
{
//Set the clear color (black)
glClearColor(0.0,0.0,0.0,1.0);
//Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
//stretch to screen
glViewport(0,0,gw,gh);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0,gw,0,gh);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//Draw a single line
// it will stretch 1/2 the width and the full vertical
//We will use a geometry shader to draw the same line, but with the x and y values swapped!
// i.e. we will get a cross on the screen
glBegin(GL_LINES);
glVertex2i(gw/2, 0);
glVertex2i(gw/2, gh);
glEnd();
//swap buffers (double buffering)
glutSwapBuffers();
}
| In the next function (which spans the next few blocks), setups up the GLSL GPU code. We create a few variables to read in the GLSL shader code, then we create a few shader objects. We then read in the GLSL shader code from file. | |
//Setup shaders
void setShaders()
{
//a few strings
// will hold onto the file read in!
char *vs = NULL, *fs = NULL, *fs2 = NULL, *gs = NULL;
//First, create our shaders
v = glCreateShader(GL_VERTEX_SHADER);
f = glCreateShader(GL_FRAGMENT_SHADER);
g = glCreateShader(GL_GEOMETRY_SHADER_EXT);
//Read in the programs
vs = textFileRead("GLSL/toon.vert");
fs = textFileRead("GLSL/toon.frag");
gs = textFileRead("GLSL/toon.geom");
| We then specify the shader sources and then compile the code. | |
//Setup a few constant pointers for below
const char * ff = fs;
const char * vv = vs;
const char * gg = gs;
glShaderSource(v, 1, &vv, NULL);
glShaderSource(f, 1, &ff, NULL);
glShaderSource(g, 1, &gg, NULL);
free(vs);free(fs);free(gs);
glCompileShader(v);
glCompileShader(f);
glCompileShader(f2);
glCompileShader(g);
| We then create a program (contains vertex, geometry, and fragment programs). We attach the shaders to the program. | |
p = glCreateProgram();
glAttachShader(p,f);
glAttachShader(p,v);
glAttachShader(p,g);
| We then specify the geometry input and output primitive types. We will pass in GL_LINES, and will output GL_LINE_STRIPs from the geometry program. I think you can use any of the OpenGL primitive types as the third parameter to the first function below, plus four new types specified by geometry shaders (don't know if they are isolated to geometry shaders). You can read about these types in the OpenGL documentation from the NVIDIA link we provided at the top! I dont like that you have to specify the types, but my tears dont drive hardware!
We then get the maximum number of vertices that a geometry program can output, and pass it to the GPU. We think our method is inefficent, i.e. we should pass the real number of vertices we expect to generate. We are waiting to find out if overspecifying the number is bad! | |
glProgramParameteriEXT(p,GL_GEOMETRY_INPUT_TYPE_EXT,GL_LINES);
glProgramParameteriEXT(p,GL_GEOMETRY_OUTPUT_TYPE_EXT,GL_LINE_STRIP);
int temp;
glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT,&temp);
glProgramParameteriEXT(p,GL_GEOMETRY_VERTICES_OUT_EXT,temp);
| We then link our programs and make them active. We then check for errors. | |
glLinkProgram(p);
glUseProgram(p);
printShaderInfoLog(v);
printShaderInfoLog(f);
printShaderInfoLog(f2);
printShaderInfoLog(g);
printProgramInfoLog(p);
}
| In main, we setup GLUT. | |
int main(int argc, char **argv)
{
glutInit(&argc, argv);
//glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("GPU");
glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);
glutReshapeFunc(changeSize);
glutKeyboardFunc(processNormalKeys);
| We now setup GLEW and see if we hae the necessary OpenGL version support. | |
glewInit();
if (glewIsSupported("GL_VERSION_2_1"))
printf("Ready for OpenGL 2.1/n");
else {
printf("OpenGL 2.1 not supported/n");
exit(1);
}
if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader && GL_EXT_geometry_shader4)
printf("Ready for GLSL - vertex, fragment, and geometry units/n");
else {
printf("Not totally ready :( /n");
exit(1);
}
| We now setup the shader, and call the GLUT main loop. | |
setShaders();
glutMainLoop();
// just for compatibiliy purposes
return 0;
}
| This is our vertex program. Its a simple pass through. | |
void main()
{
//Transform the vertex (ModelViewProj matrix)
gl_Position = ftransform();
}
| Here is our geometry shader GLSL program. | |
#version 120
#extension GL_EXT_geometry_shader4 : enable
void main(void)
{
//increment variable
int i;
/
//This example has two parts
// step a) draw the primitive pushed down the pipeline
// there are gl_Vertices # of vertices
// put the vertex value into gl_Position
// use EmitVertex => 'create' a new vertex
// use EndPrimitive to signal that you are done creating a primitive!
// step b) create a new piece of geometry (I.E. WHY WE ARE USING A GEOMETRY SHADER!)
// I just do the same loop, but swizzle the x and y values
// result => the line we want to draw, and the same line, but along the other axis
//Pass-thru!
for(i=0; i< gl_VerticesIn; i++){
gl_Position = gl_PositionIn[i];
EmitVertex();
}
EndPrimitive();
//New piece of geometry! We just swizzle the x and y terms
for(i=0; i< gl_VerticesIn; i++){
gl_Position = gl_PositionIn[i];
gl_Position.xy = gl_Position.yx;
EmitVertex();
}
eNdPrimitive();
/
}
| Here is our fragment program. It just shades a pixel a constant color. We just want to demonstrate using the geometry shader. | |
void main()
{
//Yeah, yeah, yeah ... we just color the pixel
// this example is showing off geometry shaders, not fragments!
//Shade to blue!
gl_FragColor = vec4(0.0,0.0,1.0,1.0);
}