Chapter 2: Pulling Out OpenGL

In the last chapter, we looked at our view controller, and the OpenGL code that we’d like to pull out and put in its own class. Since we decided that the attributes and uniforms would be the only tricky part, let’s start with those.

Shader attributes are per-vertex variables that we need to pass into the vertex shader, like the position and color, in the case of the Apple template OpenGL ES 2.0 application. We tell OpenGL what the identifiers for the attributes should be. Shader uniforms are variables that don’t change throughout the render process, and are available to the vertex shader and the fragment shader. After shader linking, we need to ask OpenGL where it put those uniforms, and assign those values to variables that we’ll need to keep track of to use those uniforms properly.

From an architectural point of view, however, we don’t need attributes and uniforms to behave differently from each other. The programer utilizing this new OpenGL program class should be able to simply create attributes and uniforms and add them to the program, programmatically. With that in mind, we’ll encapsulate all of the logic differences in the helper classes for attributes and uniforms.

Let’s design an attribute helper class first. Here’s the class header file.

#import <Foundation/Foundation.h>

#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>

@interface EDShaderAttribute : NSObject {
    GLuint myAttributeId;       // ID of this attribute
    NSString *myAttributeName;  // Name of this attribute
}

@property (nonatomic, assign) GLuint attributeId;
@property (nonatomic, assign) NSString *attributeName;

- (EDShaderAttribute *)initAttributeWithID:(GLuint)newAttributeId andName:(NSString *)newAttributeName;

@end

We’ll need to remember our ID, and a human-friendly name to refer back to us. We’ll expose these as properties for our OpenGL program class to access, and define a single method, initAttributeWithID:andName: as a constructor.

The programmer using the openGL program class will not access this EDShaderAttribute object directly, it will be managed by the OpenGL program class.

The actual code for this object is also pretty straightforward.

#import "EDShaderAttribute.h"

@implementation EDShaderAttribute

@synthesize attributeId = myAttributeId;
@synthesize attributeName = myAttributeName;

// The purpose of this class is to allow the EDOpenGLProgram class to be as flexible as possible.
// As we dynamically add attributes through the EDOpenGLProgram class, it in turn
// utilizes this class to manage those attributes.

- (EDShaderAttribute *)initAttributeWithID:(GLuint)newAttributeId andName:(NSString *)newAttributeName {
    if((self = [super init])) {
        myAttributeId = newAttributeId;
        myAttributeName = newAttributeName;
    }
   
    return self;
}

@end

I like to prefix instance variables with “my”, since it helps me keep track of my instance variables, but they look pretty goofy when exposed as properties. The @synthesize keyword allows me to not only generate getter and setter methods, but also specify the name. By adding the “attributeId = “ in front of my instance variable, the myAttributeId will be accessed as “attributeId” instead. We’ll see an example of that shortly.

Since we define the numeric identifier of attributes, this class only has to remember the identifier and a human-friendly name (“ATTRIB_VERTEX”, for example).

The shader uniform class is very similar, but has one small addition.

#import <Foundation/Foundation.h>

#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>

@interface EDShaderUniform : NSObject {
    GLuint myUniformId;         // ID of this uniform
    NSString *myUniformName;    // Name of this uniform
    GLuint myUniformLocation;   // Location of uniform as returned from OpenGL
}

@property (nonatomic, assign) GLuint uniformId;
@property (nonatomic, assign) NSString *uniformName;
@property (nonatomic, assign) GLuint uniformLocation;

- (EDShaderUniform *)initUniformWithID:(GLuint)newUniformId andName:(NSString *)newUniformName;

@end

Like the attribute class, the uniform class has an identifier and a human-friendly name. Unlike the attribute class, the uniform class won’t know exactly where it is until after the shaders link, so we’ll need one other piece of information. The myUniformLocation instance variable will be populated with the uniform location from OpenGL after the shaders link.

#import "EDShaderUniform.h"

@implementation EDShaderUniform

@synthesize uniformId = myUniformId;
@synthesize uniformName = myUniformName;
@synthesize uniformLocation = myUniformLocation;

// The purpose of this class is to allow the EDOpenGLProgram class to be as flexible as possible.
// As we dynamically add uniforms through the EDOpenGLProgram class, it in turn
// utilizes this class to manage those uniforms.

- (EDShaderUniform *)initUniformWithID:(GLuint)newUniformId andName:(NSString *)newUniformName {
    if((self = [super init])) {
        myUniformId = newUniformId;
        myUniformName = newUniformName;
        myUniformLocation = 0;
    }
   
    return self;
}

@end

Once again, remarkably similar to the attribute class, with the addition of the uniform location variable, and the initialization code defaulting it to zero.

How will these tiny helper classes be used? Let’s create our new class for abstracting the OpenGL program logic and find out. Here’s the header file for the new EDOpenGLProgram class.

#import <Foundation/Foundation.h>

#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>

#import "EDShaderAttribute.h"
#import "EDShaderUniform.h"

@interface EDOpenGLProgram : NSObject {
    GLuint myProgramId;         // My OpenGL program identifier
   
    NSString *myVertexShader;   // Associated vertex shader
    NSString *myFragmentShader; // Associated fragment shader
   
    NSMutableDictionary *myAttributes;  // Shader attributes
    NSMutableDictionary *myUniforms;    // Shader uniforms
   
    GLint currentAttributeIndex;    // Variable for tracking attributes
    GLint currentUniformIndex;      // Variable for tracking uniforms
}

@property (nonatomic, readonly) GLuint programId;

- (EDOpenGLProgram *)init;

- (void)setVertexShader:(NSString *)newVertexShader;
- (void)setFragmentShader:(NSString *)newFragmentShader;
- (void)addAttributeLocation:(NSString *)newIndex forAttribute:(NSString *)newName;
- (void)addUniformLocation:(NSString *)newIndex forUniform:(NSString *)newName;

- (GLuint)getAttributeIDForIndex:(NSString *)index;
- (GLuint)getUniformIDForIndex:(NSString *)index;

- (BOOL)compileAndLink;

- (BOOL)compileShader:(GLuint *)shader ofType:(GLenum)type fromFile:(NSString *)file;
- (BOOL)linkProgram;
- (BOOL)validate;

@end

At a glance, we can see that this class will remember a program identifier, vertex and fragment shader names, and an array each for attribute and uniform helper objects.

Even the method definitions look familiar. Except for the methods to handle the dynamic creation of attributes and uniforms, everything else is pulled almost directly from the template OpenGL ES application’s view controller.

Let’s go through the new EDOpenGLProgram class now, one method at a time.

#import "EDOpenGLProgram.h"

@implementation EDOpenGLProgram

@synthesize programId = myProgramId;

// Standard initialization with default settings

- (EDOpenGLProgram *)init {
    if((self = [super init])) {
       
        myAttributes = nil;
        myUniforms = nil;
       
        currentAttributeIndex = 0;
        currentUniformIndex = 0;
       
        myProgramId = 0;
    }
   
    return self;
}

Standard class header file import and @implementation line, and I’m using the “programId = myProgramId” synthesize trick to expose the “myProgramId” instance variable without the “my” prefix.

The init method is pretty standard, and just calls [super init] and defaults our instance variables. The myAttributes and myUniforms variables will point to arrays of attribute and uniform objects, respectively, and the currentAttributeIndex and currentUniformIndex track the current attribute and uniform values added so far. The myProgramId variable will eventually hold an OpenGL program identifier after the compileAndLink method runs.

// Set the name of the vertex shader

- (void)setVertexShader:(NSString *)newVertexShader {
    myVertexShader = newVertexShader;
}

// Set the name of the fragment shader

- (void)setFragmentShader:(NSString *)newFragmentShader {
    myFragmentShader = newFragmentShader;
}

Since the next two methods work in an identical fashion, we’ll look at them together. The setVertexShader takes the name of the vertex shader I want to link to this OpenGL program and assigns it to the myVertexShader instance variable. The setFragmentShader method does exactly the same thing for the fragment shader.

The next two methods are also identical in function.

// Add an attribute to use when compiling, linking, and utilizing this program

- (void)addAttributeLocation:(NSString *)newIndex forAttribute:(NSString *)newName {
    // If the attributes array is not initialized, do so now
   
    if(myAttributes == nil) {
        myAttributes = [[NSMutableDictionary alloc] init];
    }
   
    // Create a new attribute and add it to the attributes array using the key
    // passed in
   
    EDShaderAttribute *newAttribute = [[EDShaderAttribute alloc] initAttributeWithID:currentAttributeIndex andName:newName];
   
    [myAttributes setObject:newAttribute forKey:newIndex];
   
    currentAttributeIndex++;
}

// Add a uniform to use when compiling, linking, and utilizing this program

- (void)addUniformLocation:(NSString *)newIndex forUniform:(NSString *)newName {
    // If the uniforms array is not initialized, do so now
   
    if(myUniforms == nil) {
        myUniforms = [[NSMutableDictionary alloc] init];
    }
   
    // Create a new uniform and add it to the uniforms array using the key passed in
   
    EDShaderUniform *newUniform = [[EDShaderUniform alloc] initUniformWithID:currentUniformIndex andName:newName];
   
    [myUniforms setObject:newUniform forKey:newIndex];
   
    currentUniformIndex++;
}

The addAttributeLocation:forAttribute: and addUniformLocation:forUniform: methods each take a human-friendly name (like “ATTRIB_VERTEX” or “UNIFORM_TRANSLATE”) and the name of the corresponding shader variable (like “position” or “translate”).

We’ll look at the addUniformLocation:forUniform: method in detail, but the addAttributeLocation:forAttribute: method will work exactly the same way.

The first thing the addUniformLocation:forUniform: method does is check to see if the myUniforms array is nil. If it is, this must be the first uniform we’ve added, so we need to also initialize the array to contain the uniform objects. Waiting to initialize a container object like NSMutableDictionary until it’s actually needed is known as “lazy initialization”, and can help lower memory usage if not all container objects will be needed.

Once we’re sure we have an allocated myUniforms array, we instantiate a new EDShaderUniform object with the currentUniformIndex variable and the name of the uniform variable in the shader. We use the human-friendly name as the key to our NSMutableDictionary array so we can easily find this uniform later. We’ll see how this helps us out soon.

Once we’re finished creating and storing the new EDShaderUniform object, we increment our currentUniformIndex and exit the method.

Since we’ll eventually need this attribute and uniform information back, we’ll need to create a couple of methods to retrieve that information.

// Return the attribute ID for the specified name

- (GLuint)getAttributeIDForIndex:(NSString *)index {
    if(myAttributes != nil) {
        EDShaderAttribute *thisAttribute = [myAttributes objectForKey:index];
       
        if(thisAttribute != nil) {
            return [thisAttribute attributeId];
        }
    }
   
    return 0;
}

// return the uniform ID for the specified name

- (GLuint)getUniformIDForIndex:(NSString *)index {
    if(myUniforms != nil) {
        EDShaderUniform *thisUniform = [myUniforms objectForKey:index];
       
        if(thisUniform != nil) {
            return [thisUniform uniformLocation];
        }
    }
   
    return 0;
}

Once again, the code for handling attributes is identical to the code for handling uniforms, and we’re going to look at the uniform code in detail.

At this point, you might be thinking “if it’s all identical, why did you make separate classes?” It’s a good question, and a single “EDShaderVariable” class probably could have worked nicely, but we’d still have to differentiate between attributes and uniforms, and uniform types would still require a bit of extra processing after the shader link.

My philosophy is pretty basic: a class should apply to not only a type of structure, but the idea behind that structure as well. Since OpenGL uses attributes and uniforms for different purposes, it felt right to make them their own classes, even though the code in those classes may only differ by a single line.

For the getUniformIDForIndex: method, we first make sure that the myUniforms array isn’t nil, then try to pull out the uniform that matches the key we passed in (for example, “UNIFORM_TRANSLATE”). if we find an object stored in the myUniforms array with a matching key, we return it.

The next method, compileAndLink pulls all of this together.

// Compile and link this OpenGL program

- (BOOL)compileAndLink {
    GLuint vshID, fshID;    // Vector and fragment shader IDs
    NSString *vshPath, *fshPath;    // Vector and fragmenrt shader paths (file locations)
   
    myProgramId = glCreateProgram(); // Create OpenGL program shell
   
    // Create and compile the vertex shader
   
    vshPath = [[NSBundle mainBundle] pathForResource:myVertexShader ofType:@"vsh"];
   
    if(![self compileShader:&vshID ofType:GL_VERTEX_SHADER fromFile:vshPath]) {
        NSLog(@"Vertex shader [%@] compile failed!", myVertexShader);
        return FALSE;
    }
   
    // Create and compile the fragment shader
   
    fshPath = [[NSBundle mainBundle] pathForResource:myFragmentShader ofType:@"fsh"];
   
    if(![self compileShader:&fshID ofType:GL_FRAGMENT_SHADER fromFile:fshPath]) {
        NSLog(@"Fragment shader [%@] compile failed!", myFragmentShader);
        return FALSE;
    }
   
    // Attach shaders to program
   
    glAttachShader(myProgramId, vshID);
    glAttachShader(myProgramId, fshID);
   
    // Bind attribute location(s) - do this BEFORE linking
   
    for(NSString *key in myAttributes) {
        EDShaderAttribute *thisAttribute = [myAttributes objectForKey:key];
        glBindAttribLocation(myProgramId, [thisAttribute attributeId], [[thisAttribute attributeName] UTF8String]);
    }
   
    // Link it all together
   
    if(![self linkProgram]) {
        // if the link fails, write out a useless error message and clean up
        // any resources we may have allocated so far (shaders, program)
       
        NSLog(@"Program [%d] link failed!", myProgramId);
       
        if(vshID) {
            glDeleteShader(vshID);
            vshID = 0;
        }
       
        if(fshID) {
            glDeleteShader(fshID);
            fshID = 0;
        }
       
        if(myProgramId) {
            glDeleteProgram(myProgramId);
            myProgramId = 0;
        }
       
        return FALSE;
    }
   
    // After a successful link, get uniform locations from OpenGL - do this
    // AFTER linking
   
    for(NSString *key in myUniforms) {
        EDShaderUniform *thisUniform = [myUniforms objectForKey:key];
        [thisUniform setUniformLocation:glGetUniformLocation(myProgramId, [[thisUniform uniformName] UTF8String])];
    }
   
    // We're finished, so clean up any unneeded resources
   
    if(vshID) {
        glDeleteShader(vshID);
        vshID = 0;
    }
   
    if(fshID) {
        glDeleteShader(fshID);
        fshID = 0;
    }
   
    return TRUE;
}

This is basically the loadShaders method from the view controller in the OpenGL ES template application. We’ve modified it to use the attribute and uniform arrays, as well as instance variables for the shader names.

Let’s look at the method one chunk at a time.

    GLuint vshID, fshID;    // Vector and fragment shader IDs
    NSString *vshPath, *fshPath;    // Vector and fragmenrt shader paths (file locations)
   
    myProgramId = glCreateProgram(); // Create OpenGL program shell

The first thing we do is create an OpenGL program with the glCreateProgram() function and store the result in the myProgramId instance variable.

    // Create and compile the vertex shader
   
    vshPath = [[NSBundle mainBundle] pathForResource:myVertexShader ofType:@"vsh"];
   
    if(![self compileShader:&vshID ofType:GL_VERTEX_SHADER fromFile:vshPath]) {
        NSLog(@"Vertex shader [%@] compile failed!", myVertexShader);
        return FALSE;
    }

The next thing we do is use our myVertexShader instance variable to read in and compile our vertex shader. We’ll look at the compileShader:ofType:fromFile: method in a bit.

If the compile fails, we log a message and return FALSE.

    // Create and compile the fragment shader
   
    fshPath = [[NSBundle mainBundle] pathForResource:myFragmentShader ofType:@"fsh"];
   
    if(![self compileShader:&fshID ofType:GL_FRAGMENT_SHADER fromFile:fshPath]) {
        NSLog(@"Fragment shader [%@] compile failed!", myFragmentShader);
        return FALSE;
    }

We do the same thing for the fragment shader.

    // Attach shaders to program
   
    glAttachShader(myProgramId, vshID);
    glAttachShader(myProgramId, fshID);

After the shader compiles, we attach them to the program we created earlier in the method.

    // Bind attribute location(s) - do this BEFORE linking
   
    for(NSString *key in myAttributes) {
        EDShaderAttribute *thisAttribute = [myAttributes objectForKey:key];
        glBindAttribLocation(myProgramId, [thisAttribute attributeId], [[thisAttribute attributeName] UTF8String]);
    }

Up until this point, we’ve been pretty close to the original template code. Here, though, we’ve upgraded the process from hardcoded attribute identifiers and shader variable names to our array of EDShaderAttribute objects.

This logic will loop through the EDShaderAttribute objects in our myAttributes array and use the attribute identifier and name to call the glBindAttribLocation() function. Remember, the attribute identifier is just a number that we were incrementing in the EDOpenGLProgram class. OpenGL wants us to supply the attribute ID and shader variable name. The human-friendly name we supplied was only for the programmer to easily identify the attribute in the code.

Whatever attributes were added by the programmer will be bound now. The UTF8String message simply makes sure that the shader variable name is in the correct string format for the glBindAttribLocation() function.

The code after this is almost identical to the template code.

    // Link it all together
   
    if(![self linkProgram]) {
        // if the link fails, write out a useless error message and clean up
        // any resources we may have allocated so far (shaders, program)
       
        NSLog(@"Program [%d] link failed!", myProgramId);
       
        if(vshID) {
            glDeleteShader(vshID);
            vshID = 0;
        }
       
        if(fshID) {
            glDeleteShader(fshID);
            fshID = 0;
        }
       
        if(myProgramId) {
            glDeleteProgram(myProgramId);
            myProgramId = 0;
        }
       
        return FALSE;
    }

We call our linkProgram method, which is almost identical to the code from the template view controller, but using an instance variable for the program identifier instead of passing it in.

If the link fails, we log an error and free resources.

    // After a successful link, get uniform locations from OpenGL - do this
    // AFTER linking
   
    for(NSString *key in myUniforms) {
        EDShaderUniform *thisUniform = [myUniforms objectForKey:key];
        [thisUniform setUniformLocation:glGetUniformLocation(myProgramId, [[thisUniform uniformName] UTF8String])];
    }

If the link is successful, we now loop through our uniform objects. Unlike the attributes, which we were binding by identifier, the uniforms loop will be asking OpenGL for the uniform locations and updating the EDShaderUniform objects with the new information. We use the glGetUniformLocation() function to retrieve the uniform locations for each object in our myUniforms array.

We’ll see how this all comes together in our modified drawFrame method later.

    // We're finished, so clean up any unneeded resources
   
    if(vshID) {
        glDeleteShader(vshID);
        vshID = 0;
    }
   
    if(fshID) {
        glDeleteShader(fshID);
        fshID = 0;
    }
   
    return TRUE;

Finally, we free the shaders which are no longer needed after linking. This code is right out of the template as well.

Next, we’ll take a quick look at the compileShader:ofType:fromFile: method.

// Compile a specific shader

- (BOOL)compileShader:(GLuint *)shader ofType:(GLenum)type fromFile:(NSString *)file {
    GLint status;
    const GLchar *source;
   
    // Pull shader source code out of file as a UTF-8 string (as required by OpenGL)
   
    source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
   
    if(!source) {
        NSLog(@"Failed to load shader [%@]!", file);
        return FALSE;
    }
   
    // Now that that's over, create a shader shell, load it with source code,
    // and compile it.
   
    *shader = glCreateShader(type);
   
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
   
    // If we're in debug mode (that is, we've defined "DEBUG"), print out a
    // shader compile log. Should NOT be done for production code, it slows everything
    // down.
   
#if defined(DEBUG)
    GLint logLength;
   
    // Ask OpenGL for the size of the compile log - it will be up to us
    // to provide space for it.
   
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
   
    if(logLength > 0) {
        // There's a log to inspect, so let's allocate space for it and
        // list it out
       
        GLchar *log = (GLchar *)malloc(logLength);
       
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
       
        NSLog(@"Shader compile log:");
        NSLog(@"%s", log);
       
        // Finished with that, free the memory we just allocated
       
        free(log);
    }
#endif
   
    // Check shader status to verify compile was successful
   
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
   
    if(status == 0) {
        // A problem occurred - define DEBUG and have a look at that log
        // created above after re-running
       
        glDeleteShader(*shader);
        return FALSE;
    }
   
    return TRUE;
}

This code is nearly identical to the template code, as are the next two methods, linkProgram and validate.

// Link the OpenGL program

- (BOOL)linkProgram {
    GLint status;
   
    glLinkProgram(myProgramId);
   
#if defined(DEBUG)
    GLint logLength;
   
    // Ask OpenGL for the size of the link log - it will be up to us
    // to provide space for it.
   
    glGetProgramiv(myProgramId, GL_INFO_LOG_LENGTH, &logLength);
   
    if(logLength > 0) {
        // There's a log to inspect, so let's allocate space for it and
        // list it out
       
        GLchar *log = (GLchar *)malloc(logLength);
       
        glGetProgramInfoLog(myProgramId, logLength, &logLength, log);
       
        NSLog(@"Program link log:");
        NSLog(@"%s", log);
       
        // Finished with that, free the memory we just allocated
       
        free(log);
    }
#endif
   
    glGetProgramiv(myProgramId, GL_LINK_STATUS, &status);

    if(status == 0) {
        // A problem occured - define DEBUG and have a look at that log
        // created above after re-running
       
        return FALSE;
    }
   
    return TRUE;

}

- (BOOL)validate {
    GLint logLength, status;
   
    glValidateProgram(myProgramId);
   
    // Ask OpenGL for the size of the validate log - it will be up to us
    // to provide space for it.
   
    glGetProgramiv(myProgramId, GL_INFO_LOG_LENGTH, &logLength);
   
    if(logLength > 0) {
        // There's a log to inspect, so let's allocate space for it and
        // list it out
       
        GLchar *log = (GLchar *)malloc(logLength);
       
        glGetProgramInfoLog(myProgramId, logLength, &logLength, log);
       
        NSLog(@"Program validate log:");
        NSLog(@"%s", log);
       
        // Finished with that, free the memory we just allocated
       
        free(log);
    }
   
    glGetProgramiv(myProgramId, GL_VALIDATE_STATUS, &status);
   
    if(status == 0) {
        return FALSE;
    }
   
    return TRUE;

}

For a refresher on what these methods are doing, check out Part Four: Creating the OpenGL Environment in the previous tutorial.

- (void)dealloc {
    if(myProgramId) {
        glDeleteProgram(myProgramId);
        myProgramId = 0;
    }
   
    [super dealloc];
}

Finally, in the dealloc method, free resources by deleting the OpenGL program we created.

Now that we have our new classes ready to go, we’ll modify the template code to use them in the next chapter.

Chapter 1 | Index | Chapter 3