summaryrefslogtreecommitdiffstats
path: root/lib/gltools.cpp
diff options
context:
space:
mode:
authorKyle K <kylek389@gmail.com>2010-10-03 18:30:04 -0500
committerKamil Kaminski <kamilkss@gmail.com>2010-10-03 18:30:04 -0500
commit163b963b1d6c1d0d288e27aeb978ff521a241917 (patch)
treed4b03049e501f8f068e4cba3a910c1fb04232ad5 /lib/gltools.cpp
downloadGLPyramid++-163b963b1d6c1d0d288e27aeb978ff521a241917.tar.gz
GLPyramid++-163b963b1d6c1d0d288e27aeb978ff521a241917.tar.bz2
GLPyramid++-163b963b1d6c1d0d288e27aeb978ff521a241917.zip
Initial commit
Diffstat (limited to 'lib/gltools.cpp')
-rw-r--r--lib/gltools.cpp651
1 files changed, 651 insertions, 0 deletions
diff --git a/lib/gltools.cpp b/lib/gltools.cpp
new file mode 100644
index 0000000..bcdd957
--- /dev/null
+++ b/lib/gltools.cpp
@@ -0,0 +1,651 @@
+/*
+ * gltools.c
+ * Block
+ *
+ * Created by Richard Wright on 10/16/06.
+ * OpenGL SuperBible, 4th Edition
+ *
+ */
+
+#include "gltools.h"
+#include "math3d.h"
+#include <assert.h>
+
+///////////////////////////////////////////////////////////////////////////////
+// Get the OpenGL version number
+bool gltGetOpenGLVersion(int &nMajor, int &nMinor)
+{
+ const char *szVersionString = (const char *)glGetString(GL_VERSION);
+ if (szVersionString == NULL)
+ {
+ nMajor = 0;
+ nMinor = 0;
+ return false;
+ }
+
+ // Get major version number. This stops at the first non numeric character
+ nMajor = atoi(szVersionString);
+
+ // Get minor version number. Start past the first ".", atoi terminates on first non numeric char.
+ nMinor = atoi(strstr(szVersionString, ".")+1);
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// This function determines if the named OpenGL Extension is supported
+// Returns 1 or 0
+int gltIsExtSupported(const char *extension)
+{
+ GLubyte *extensions = NULL;
+ const GLubyte *start;
+ GLubyte *where, *terminator;
+
+ where = (GLubyte *) strchr(extension, ' ');
+ if (where || *extension == '\0')
+ return 0;
+
+ extensions = (GLubyte *)glGetString(GL_EXTENSIONS);
+
+ start = extensions;
+ for (;;)
+ {
+ where = (GLubyte *) strstr((const char *) start, extension);
+
+ if (!where)
+ break;
+
+ terminator = where + strlen(extension);
+
+ if (where == start || *(where - 1) == ' ')
+ {
+ if (*terminator == ' ' || *terminator == '\0')
+ return 1;
+ }
+ start = terminator;
+ }
+ return 0;
+}
+
+#ifdef _WIN32
+///////////////////////////////////////////////////////////////////////////////
+// Win32 Only, check for WGL extension
+#include "wglext.h"
+int gltIsWGLExtSupported(HDC hDC, const char *szExtension)
+{
+ GLubyte *extensions = NULL;
+ const GLubyte *start;
+ GLubyte *where, *terminator;
+ PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB;
+
+ // Just look or the entry point
+ wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
+ if (wglGetExtensionsStringARB == NULL)
+ return 0;
+
+ where = (GLubyte *) strchr(szExtension, ' ');
+ if (where || *szExtension == '\0')
+ return 0;
+
+ extensions = (GLubyte *)wglGetExtensionsStringARB(hDC);
+
+ start = extensions;
+ for (;;)
+ {
+ where = (GLubyte *) strstr((const char *) start, szExtension);
+
+ if (!where)
+ break;
+
+ terminator = where + strlen(szExtension);
+
+ if (where == start || *(where - 1) == ' ')
+ {
+ if (*terminator == ' ' || *terminator == '\0')
+ return 1;
+ }
+ start = terminator;
+ }
+ return 0;
+}
+#endif
+
+/////////////////////////////////////////////////////////////
+// Get a pointer to an OpenGL extension
+// Note on the Mac, this does a lot of work that could be saved
+// if you call this function repeatedly. Write your own function that
+// gets the bundle once, gets all the function pointers, then releases
+// the bundle.
+void *gltGetExtensionPointer(const char *szExtensionName)
+{
+#ifdef WIN32
+ // Well, this one is simple. An OpenGL context must be
+ // current first. Returns NULL if extension not supported
+ return (void *)wglGetProcAddress(szExtensionName);
+#endif
+
+#ifdef linux
+ // Pretty much ditto above
+ return (void *)glXGetProcAddress((GLubyte *)szExtensionName);
+#endif
+
+#ifdef __APPLE__
+ // Mac is a bit more tricky.
+ // First we need the bundle
+ CFBundleRef openGL = 0;
+ SInt16 fwVersion = 0;
+ SInt32 fwDir = 0;
+
+ if (FindFolder(kSystemDomain, kFrameworksFolderType, kDontCreateFolder, &fwVersion, &fwDir) != noErr)
+ return NULL;
+
+ FSSpec fSpec;
+ FSRef fRef;
+ if (FSMakeFSSpec(fwVersion, fwDir, "\pOpenGL.framework", &fSpec) != noErr)
+ return NULL;
+
+ FSpMakeFSRef(&fSpec, &fRef);
+ CFURLRef url = CFURLCreateFromFSRef(kCFAllocatorDefault, &fRef);
+ if (!url)
+ return NULL;
+
+ openGL = CFBundleCreate(kCFAllocatorDefault, url);
+ CFRelease(url);
+
+ // Then load the function pointer from the bundle
+ CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, szExtensionName, kCFStringEncodingMacRoman);
+ void *pFunc = CFBundleGetFunctionPointerForName(openGL, string);
+
+ // Release the bundle and string
+ CFRelease(string);
+ CFRelease(openGL);
+
+ // Return the function ponter
+ return pFunc;
+#endif
+}
+
+///////////////////////////////////////////////////////////////////
+// Draw the unit axis. A small white sphere represents the origin
+// and the three axes are colored Red, Green, and Blue, which
+// corresponds to positive X, Y, and Z respectively. Each axis has
+// an arrow on the end, and normals are provided should the axes
+// be lit. These are all built using the quadric shapes. For best
+// results, put this in a display list.
+void gltDrawUnitAxes(void)
+{
+ GLUquadricObj *pObj; // Temporary, used for quadrics
+
+ // Measurements
+ float fAxisRadius = 0.025f;
+ float fAxisHeight = 1.0f;
+ float fArrowRadius = 0.06f;
+ float fArrowHeight = 0.1f;
+
+ // Setup the quadric object
+ pObj = gluNewQuadric();
+ gluQuadricDrawStyle(pObj, GLU_FILL);
+ gluQuadricNormals(pObj, GLU_SMOOTH);
+ gluQuadricOrientation(pObj, GLU_OUTSIDE);
+ gluQuadricTexture(pObj, GLU_FALSE);
+
+ ///////////////////////////////////////////////////////
+ // Draw the blue Z axis first, with arrowed head
+ glColor3f(0.0f, 0.0f, 1.0f);
+ gluCylinder(pObj, fAxisRadius, fAxisRadius, fAxisHeight, 10, 1);
+ glPushMatrix();
+ glTranslatef(0.0f, 0.0f, 1.0f);
+ gluCylinder(pObj, fArrowRadius, 0.0f, fArrowHeight, 10, 1);
+ glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
+ gluDisk(pObj, fAxisRadius, fArrowRadius, 10, 1);
+ glPopMatrix();
+
+ ///////////////////////////////////////////////////////
+ // Draw the Red X axis 2nd, with arrowed head
+ glColor3f(1.0f, 0.0f, 0.0f);
+ glPushMatrix();
+ glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
+ gluCylinder(pObj, fAxisRadius, fAxisRadius, fAxisHeight, 10, 1);
+ glPushMatrix();
+ glTranslatef(0.0f, 0.0f, 1.0f);
+ gluCylinder(pObj, fArrowRadius, 0.0f, fArrowHeight, 10, 1);
+ glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
+ gluDisk(pObj, fAxisRadius, fArrowRadius, 10, 1);
+ glPopMatrix();
+ glPopMatrix();
+
+ ///////////////////////////////////////////////////////
+ // Draw the Green Y axis 3rd, with arrowed head
+ glColor3f(0.0f, 1.0f, 0.0f);
+ glPushMatrix();
+ glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
+ gluCylinder(pObj, fAxisRadius, fAxisRadius, fAxisHeight, 10, 1);
+ glPushMatrix();
+ glTranslatef(0.0f, 0.0f, 1.0f);
+ gluCylinder(pObj, fArrowRadius, 0.0f, fArrowHeight, 10, 1);
+ glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
+ gluDisk(pObj, fAxisRadius, fArrowRadius, 10, 1);
+ glPopMatrix();
+ glPopMatrix();
+
+ ////////////////////////////////////////////////////////
+ // White Sphere at origin
+ glColor3f(1.0f, 1.0f, 1.0f);
+ gluSphere(pObj, 0.05f, 15, 15);
+
+ // Delete the quadric
+ gluDeleteQuadric(pObj);
+}
+
+// For best results, put this in a display list
+// Draw a torus (doughnut) at z = fZVal... torus is in xy plane
+void gltDrawTorus(GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor)
+{
+ M3DVector3f vNormal;
+ double majorStep = 2.0f*M3D_PI / numMajor;
+ double minorStep = 2.0f*M3D_PI / numMinor;
+ int i, j;
+
+ for (i=0; i<numMajor; ++i)
+ {
+ double a0 = i * majorStep;
+ double a1 = a0 + majorStep;
+ GLfloat x0 = (GLfloat) cos(a0);
+ GLfloat y0 = (GLfloat) sin(a0);
+ GLfloat x1 = (GLfloat) cos(a1);
+ GLfloat y1 = (GLfloat) sin(a1);
+
+ glBegin(GL_TRIANGLE_STRIP);
+ for (j=0; j<=numMinor; ++j)
+ {
+ double b = j * minorStep;
+ GLfloat c = (GLfloat) cos(b);
+ GLfloat r = minorRadius * c + majorRadius;
+ GLfloat z = minorRadius * (GLfloat) sin(b);
+
+ // First point
+ glTexCoord2f((float)(i)/(float)(numMajor), (float)(j)/(float)(numMinor));
+ vNormal[0] = x0*c;
+ vNormal[1] = y0*c;
+ vNormal[2] = z/minorRadius;
+ m3dNormalizeVector(vNormal);
+ glNormal3fv(vNormal);
+ glVertex3f(x0*r, y0*r, z);
+
+ glTexCoord2f((float)(i+1)/(float)(numMajor), (float)(j)/(float)(numMinor));
+ vNormal[0] = x1*c;
+ vNormal[1] = y1*c;
+ vNormal[2] = z/minorRadius;
+ m3dNormalizeVector(vNormal);
+ glNormal3fv(vNormal);
+ glVertex3f(x1*r, y1*r, z);
+ }
+ glEnd();
+ }
+}
+
+// For best results, put this in a display list
+// Draw a sphere at the origin
+void gltDrawSphere(GLfloat fRadius, GLint iSlices, GLint iStacks)
+{
+ GLfloat drho = (GLfloat)(3.141592653589) / (GLfloat) iStacks;
+ GLfloat dtheta = 2.0f * (GLfloat)(3.141592653589) / (GLfloat) iSlices;
+ GLfloat ds = 1.0f / (GLfloat) iSlices;
+ GLfloat dt = 1.0f / (GLfloat) iStacks;
+ GLfloat t = 1.0f;
+ GLfloat s = 0.0f;
+ GLint i, j; // Looping variables
+
+ for (i = 0; i < iStacks; i++)
+ {
+ GLfloat rho = (GLfloat)i * drho;
+ GLfloat srho = (GLfloat)(sin(rho));
+ GLfloat crho = (GLfloat)(cos(rho));
+ GLfloat srhodrho = (GLfloat)(sin(rho + drho));
+ GLfloat crhodrho = (GLfloat)(cos(rho + drho));
+
+ // Many sources of OpenGL sphere drawing code uses a triangle fan
+ // for the caps of the sphere. This however introduces texturing
+ // artifacts at the poles on some OpenGL implementations
+ glBegin(GL_TRIANGLE_STRIP);
+ s = 0.0f;
+ for ( j = 0; j <= iSlices; j++)
+ {
+ GLfloat theta = (j == iSlices) ? 0.0f : j * dtheta;
+ GLfloat stheta = (GLfloat)(-sin(theta));
+ GLfloat ctheta = (GLfloat)(cos(theta));
+
+ GLfloat x = stheta * srho;
+ GLfloat y = ctheta * srho;
+ GLfloat z = crho;
+
+ glTexCoord2f(s, t);
+ glNormal3f(x, y, z);
+ glVertex3f(x * fRadius, y * fRadius, z * fRadius);
+
+ x = stheta * srhodrho;
+ y = ctheta * srhodrho;
+ z = crhodrho;
+ glTexCoord2f(s, t - dt);
+ s += ds;
+ glNormal3f(x, y, z);
+ glVertex3f(x * fRadius, y * fRadius, z * fRadius);
+ }
+ glEnd();
+
+ t -= dt;
+ }
+}
+
+// Define targa header. This is only used locally.
+#pragma pack(1)
+typedef struct
+{
+ GLbyte identsize; // Size of ID field that follows header (0)
+ GLbyte colorMapType; // 0 = None, 1 = paletted
+ GLbyte imageType; // 0 = none, 1 = indexed, 2 = rgb, 3 = grey, +8=rle
+ unsigned short colorMapStart; // First colour map entry
+ unsigned short colorMapLength; // Number of colors
+ unsigned char colorMapBits; // bits per palette entry
+ unsigned short xstart; // image x origin
+ unsigned short ystart; // image y origin
+ unsigned short width; // width in pixels
+ unsigned short height; // height in pixels
+ GLbyte bits; // bits per pixel (8 16, 24, 32)
+ GLbyte descriptor; // image descriptor
+} TGAHEADER;
+#pragma pack(8)
+
+////////////////////////////////////////////////////////////////////
+// Capture the current viewport and save it as a targa file.
+// Be sure and call SwapBuffers for double buffered contexts or
+// glFinish for single buffered contexts before calling this function.
+// Returns 0 if an error occurs, or 1 on success.
+GLint gltWriteTGA(const char *szFileName)
+{
+ FILE *pFile; // File pointer
+ TGAHEADER tgaHeader; // TGA file header
+ unsigned long lImageSize; // Size in bytes of image
+ GLbyte *pBits = NULL; // Pointer to bits
+ GLint iViewport[4]; // Viewport in pixels
+ GLenum lastBuffer; // Storage for the current read buffer setting
+
+ // Get the viewport dimensions
+ glGetIntegerv(GL_VIEWPORT, iViewport);
+
+ // How big is the image going to be (targas are tightly packed)
+ lImageSize = iViewport[2] * 3 * iViewport[3];
+
+ // Allocate block. If this doesn't work, go home
+ pBits = (GLbyte *)malloc(lImageSize);
+ if (pBits == NULL)
+ return 0;
+
+ // Read bits from color buffer
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ glPixelStorei(GL_PACK_SKIP_ROWS, 0);
+ glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
+
+ // Get the current read buffer setting and save it. Switch to
+ // the front buffer and do the read operation. Finally, restore
+ // the read buffer state
+ glGetIntegerv(GL_READ_BUFFER, (GLint *)&lastBuffer);
+ glReadBuffer(GL_FRONT);
+ glReadPixels(0, 0, iViewport[2], iViewport[3], GL_BGR_EXT, GL_UNSIGNED_BYTE, pBits);
+ glReadBuffer(lastBuffer);
+
+ // Initialize the Targa header
+ tgaHeader.identsize = 0;
+ tgaHeader.colorMapType = 0;
+ tgaHeader.imageType = 2;
+ tgaHeader.colorMapStart = 0;
+ tgaHeader.colorMapLength = 0;
+ tgaHeader.colorMapBits = 0;
+ tgaHeader.xstart = 0;
+ tgaHeader.ystart = 0;
+ tgaHeader.width = iViewport[2];
+ tgaHeader.height = iViewport[3];
+ tgaHeader.bits = 24;
+ tgaHeader.descriptor = 0;
+
+ // Do byte swap for big vs little endian
+#ifdef __APPLE__
+ LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
+ LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
+ LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
+ LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
+ LITTLE_ENDIAN_WORD(&tgaHeader.width);
+ LITTLE_ENDIAN_WORD(&tgaHeader.height);
+#endif
+
+ // Attempt to open the file
+ pFile = fopen(szFileName, "wb");
+ if (pFile == NULL)
+ {
+ free(pBits); // Free buffer and return error
+ return 0;
+ }
+
+ // Write the header
+ fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile);
+
+ // Write the image data
+ fwrite(pBits, lImageSize, 1, pFile);
+
+ // Free temporary buffer and close the file
+ free(pBits);
+ fclose(pFile);
+
+ // Success!
+ return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+// Allocate memory and load targa bits. Returns pointer to new buffer,
+// height, and width of texture, and the OpenGL format of data.
+// Call free() on buffer when finished!
+// This only works on pretty vanilla targas... 8, 24, or 32 bit color
+// only, no palettes, no RLE encoding.
+GLbyte *gltLoadTGA(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat)
+{
+ FILE *pFile; // File pointer
+ TGAHEADER tgaHeader; // TGA file header
+ unsigned long lImageSize; // Size in bytes of image
+ short sDepth; // Pixel depth;
+ GLbyte *pBits = NULL; // Pointer to bits
+
+ // Default/Failed values
+ *iWidth = 0;
+ *iHeight = 0;
+ *eFormat = GL_BGR_EXT;
+ *iComponents = GL_RGB8;
+
+ // Attempt to open the fil
+ pFile = fopen(szFileName, "rb");
+ if (pFile == NULL)
+ return NULL;
+
+ // Read in header (binary)
+ fread(&tgaHeader, 18/* sizeof(TGAHEADER)*/, 1, pFile);
+
+ // Do byte swap for big vs little endian
+#ifdef __APPLE__
+ LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
+ LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
+ LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
+ LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
+ LITTLE_ENDIAN_WORD(&tgaHeader.width);
+ LITTLE_ENDIAN_WORD(&tgaHeader.height);
+#endif
+
+ // Get width, height, and depth of texture
+ *iWidth = tgaHeader.width;
+ *iHeight = tgaHeader.height;
+ sDepth = tgaHeader.bits / 8;
+
+ // Put some validity checks here. Very simply, I only understand
+ // or care about 8, 24, or 32 bit targa's.
+ if (tgaHeader.bits != 8 && tgaHeader.bits != 24 && tgaHeader.bits != 32)
+ return NULL;
+
+ // Calculate size of image buffer
+ lImageSize = tgaHeader.width * tgaHeader.height * sDepth;
+
+ // Allocate memory and check for success
+ pBits = (GLbyte*)malloc(lImageSize * sizeof(GLbyte));
+ if (pBits == NULL)
+ return NULL;
+
+ // Read in the bits
+ // Check for read error. This should catch RLE or other
+ // weird formats that I don't want to recognize
+ if (fread(pBits, lImageSize, 1, pFile) != 1)
+ {
+ free(pBits);
+ return NULL;
+ }
+
+ // Set OpenGL format expected
+ switch(sDepth)
+ {
+ case 3: // Most likely case
+ *eFormat = GL_BGR_EXT;
+ *iComponents = GL_RGB8;
+ break;
+ case 4:
+ *eFormat = GL_BGRA_EXT;
+ *iComponents = GL_RGBA8;
+ break;
+ case 1:
+ *eFormat = GL_LUMINANCE;
+ *iComponents = GL_LUMINANCE8;
+ break;
+ };
+
+ // Done with File
+ fclose(pFile);
+
+ // Return pointer to image data
+ return pBits;
+}
+
+// Rather than malloc/free a block everytime a shader must be loaded,
+// I will dedicate a single 4k page for reading in shaders. Thanks to
+// modern OS design, this page will be swapped out to disk later if never
+// used again after program initialization. Where-as mallocing different size
+// shader blocks could lead to heap fragmentation, which would actually be worse.
+//#define MAX_SHADER_LENGTH 4096 -> This is defined in gltools.h
+static GLubyte shaderText[MAX_SHADER_LENGTH];
+
+////////////////////////////////////////////////////////////////
+// Load the shader from the specified file. Returns false if the
+// shader could not be loaded
+bool bLoadShaderFile(const char *szFile, GLhandleARB shader)
+{
+ GLint shaderLength = 0;
+ FILE *fp;
+ GLcharARB *fsStringPtr[1];
+
+ // Open the shader file
+ fp = fopen(szFile, "r");
+ if (fp != NULL)
+ {
+ // See how long the file s
+ while (fgetc(fp) != EOF)
+ shaderLength++;
+
+ // Allocate a block of memory to send in the shader
+ assert(shaderLength < MAX_SHADER_LENGTH); // make me bigger!
+ if (shaderLength > MAX_SHADER_LENGTH)
+ {
+ fclose(fp);
+ return false;
+ }
+
+ // Go back to beginning of file
+ rewind(fp);
+
+ // Read the whole file in
+ if (shaderText != NULL)
+ fread(shaderText, 1, shaderLength, fp);
+
+ // Make sure it is null terminated and close the file
+ shaderText[shaderLength] = '\0';
+ fclose(fp);
+ }
+ else
+ return false;
+
+ // Load the string
+ fsStringPtr[0] = (GLcharARB *)shaderText;
+ glShaderSourceARB(shader, 1, (const GLcharARB **)fsStringPtr, NULL);
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////
+// Load a pair of shaders, compile, and link together. Specify the complete
+// path and file name of each ASCII shader file. Note, there is no support for
+// just loading say a vertex program... you have to do both.
+GLhandleARB gltLoadShaderPair(const char *szVertexProg, const char *szFragmentProg)
+{
+ // Temporary Shader objects
+ GLhandleARB hVertexShader;
+ GLhandleARB hFragmentShader;
+ GLhandleARB hReturn = 0;
+ GLint testVal;
+
+ // Create shader objects
+ hVertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
+ hFragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
+
+ // Load them. If fail clean up and return null
+ if (bLoadShaderFile(szVertexProg, hVertexShader) == false)
+ {
+ glDeleteObjectARB(hVertexShader);
+ glDeleteObjectARB(hFragmentShader);
+ return 0;
+ }
+
+ if (bLoadShaderFile(szFragmentProg, hFragmentShader) == false)
+ {
+ glDeleteObjectARB(hVertexShader);
+ glDeleteObjectARB(hFragmentShader);
+ return 0;
+ }
+
+ // Compile them
+ glCompileShaderARB(hVertexShader);
+ glCompileShaderARB(hFragmentShader);
+
+ // Check for errors
+ glGetObjectParameterivARB(hVertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &testVal);
+ if (testVal == GL_FALSE)
+ {
+ glDeleteObjectARB(hVertexShader);
+ glDeleteObjectARB(hFragmentShader);
+ return 0;
+ }
+
+ glGetObjectParameterivARB(hFragmentShader, GL_OBJECT_COMPILE_STATUS_ARB, &testVal);
+ if (testVal == GL_FALSE)
+ {
+ glDeleteObjectARB(hVertexShader);
+ glDeleteObjectARB(hFragmentShader);
+ return 0;
+ }
+
+ // Link them - assuming it works...
+ hReturn = glCreateProgramObjectARB();
+ glAttachObjectARB(hReturn, hVertexShader);
+ glAttachObjectARB(hReturn, hFragmentShader);
+ glLinkProgramARB(hReturn);
+
+ // These are no longer needed
+ glDeleteObjectARB(hVertexShader);
+ glDeleteObjectARB(hFragmentShader);
+
+ return hReturn;
+}
+