Part Four: Creating the OpenGL Environment

We’ve generated an OpenGL ES Application shell, and we have a rainbow-colored cube moving up and down the screen, but what makes it go? Let’s follow the flow of the program and see how OpenGL ES 2.0 is initialized on the iPhone.

At program launch, our EDCubeDemoViewController class gets an awakeFromNib message shortly after instantiation. The code for that method looks like this.

- (void)awakeFromNib
{
    EAGLContext *aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   
    if (!aContext) {
        aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
    }
   
    if (!aContext)
        NSLog(@"Failed to create ES context");
    else if (![EAGLContext setCurrentContext:aContext])
        NSLog(@"Failed to set ES context current");
   
    self.context = aContext;
    [aContext release];
   
    [(EAGLView *)self.view setContext:context];
    [(EAGLView *)self.view setFramebuffer];
   
    if ([context API] == kEAGLRenderingAPIOpenGLES2)
        [self loadShaders];
   
    animating = FALSE;
    animationFrameInterval = 1;
    self.displayLink = nil;
}

Let’s look at the method code more closely.

  EAGLContext *aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

This line attempts to allocate and initialize an EAGLContext object as an OpenGL ES 2.0 context.

    if (!aContext) {
        aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
    }

This line checks to see if we got a valid OpenGL ES 2.0 context. If not, try for a valid OpenGL ES 1.1 context.

If you plan to write applications that support both OpenGL ES 1.1 and 2.0, this will be how to do it. The good news is that you will support a wider array of iOS devices, including many older iPod touches and the original iPhone and iPhone 3G. The bad news is you will have to code all rendering routines twice – once in OpenGL ES 1.1, and again in Open GL ES 2.0.

Since this tutorial will cover only OpenGL ES 2.0, we’re going to make life easier and remove any OpenGL ES 1.1 code when we start coding in the tutorial.

 if (!aContext)
        NSLog(@"Failed to create ES context");
    else if (![EAGLContext setCurrentContext:aContext])
        NSLog(@"Failed to set ES context current");

If we didn’t get a valid OpenGL ES context of any type, we’ll log a few messages. The program won’t actually ‘crash’, but it’ll pop up with a white screen and do nothing. The worst that will happen is the user will close your app and write a nasty review in the iTunes Music Store.

        self.context = aContext;
    [aContext release];

We set our context instance variable and send a release message.

    [(EAGLView *)self.view setContext:context];
    [(EAGLView *)self.view setFramebuffer];

Here we send our view the setContext: message and the setFramebuffer message.

What’s our view, you ask? If you click on the EDCubeDemoViewController.xib file in Xcode, select the view object, then look at the Identity Inspector, you’ll see that our view is of the class EAGLView.

 

Checking our view class

 

In the EAGLView.m file, we find the methods setContext: and setFramebuffer. We’ll look at those in just a moment.

    if ([context API] == kEAGLRenderingAPIOpenGLES2)
        [self loadShaders];

Here we check to see if we’re an OpenGL ES 2.0 application. If so, we need shaders, so we’ll load them here. We’ll come back to this method as well.

    animating = FALSE;
    animationFrameInterval = 1;
    self.displayLink = nil;

Finally, we set a few instance variables and we’re finished.

But wait, what was that about setting a context and a framebuffer?

In the EAGLView class, we do quite a bit of OpenGL ES initialization, for both 1.1 and 2.0. Let’s look at the setContext: method in the EAGLView class.

- (void)setContext:(EAGLContext *)newContext
{
    if (context != newContext) {
        [self deleteFramebuffer];
       
        [context release];
        context = [newContext retain];
       
        [EAGLContext setCurrentContext:nil];
    }
}

This method checks to see if the new context is equal to the current context (the current value of the context instance variable). If not, it will perform some clean up and set the context instance variable to the new context specified.

Don’t worry about the nil setCurrentContext: message being sent to EAGLContext right now, this will be fixed in the next method we’ll be looking at, setFramebuffer.

- (void)setFramebuffer
{
    if (context) {
        [EAGLContext setCurrentContext:context];
       
        if (!defaultFramebuffer)
            [self createFramebuffer];
       
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
       
        glViewport(0, 0, framebufferWidth, framebufferHeight);
    }
}

This method in the EAGLView class checks to see if the context instance variable is valued, and then sends the setCurrentContext: message to set it with EAGLContext.

If there’s no default framebuffer, we’ll create one of those too. By the way, notice that function name, glBindFramebuffer()? That little ‘gl’ at the beginning indicates that this is an OpenGL ES function.

After that, we bind the default framebuffer and set the size of our viewport, or visible area on the screen.

Wait, where did the framebufferWidth and framebufferHeight variables come from? Those are instance variables that are set in the createFramebuffer method. Let’s look at that method now.

- (void)createFramebuffer
{
    if (context && !defaultFramebuffer) {
        [EAGLContext setCurrentContext:context];
       
        // Create default framebuffer object.
        glGenFramebuffers(1, &defaultFramebuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
       
        // Create color render buffer and allocate backing store.
        glGenRenderbuffers(1, &colorRenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
        [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferWidth);
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferHeight);
       
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
       
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }
}

Lots of stuff going on here, let’s have a look.

    if (context && !defaultFramebuffer) {
        [EAGLContext setCurrentContext:context];

If we have a context value and there’s no default framebuffer, execute the framebuffer creation code. Looks like we’ll be sending the setCurrentContext: message to EAGLContext again, but I’m not sure if it’s intentional. The app runs just fine with or without this seemingly redundant message call, so it may just be an oversight on the part of the template creator.

        // Create default framebuffer object.
        glGenFramebuffers(1, &defaultFramebuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);

The glGenFramebuffer() function simply allocates the number of framebuffers specified in the first parameter and puts their ‘names’ (which is just an integer value that OpenGL uses to identify the memory area) in the second, which is an array if there’s more than one requested. In this case, we’re only asking for one framebuffer, so the second variable is not an array.

The glBindFramebuffer() function binds the newly created framebuffer to the current OpenGL context, which was specified in the bit of code just above this one.

Wait, how do we know we’re binding it to the right place when all we gave the function was a ‘GL_FRAMEBUFFER’ flag and the name of the framebuffer?

This might be a good time to mention that OpenGL is ‘stateful’, or a ‘state machine’ as it is often called. That means that once you set a value in an active context, it stays that way until you change it again.

Since we set an active context already, everything we do with openGL affects that active context, and it sticks. When I called the glBindFramebuffer() function, it bound it to whatever context was active, I didn’t need to specify which one.

        // Create color render buffer and allocate backing store.
        glGenRenderbuffers(1, &colorRenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
        [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferWidth);
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferHeight);

Here, like we did for the framebuffer, we’re creating a renderbuffer and binding it to the active context.

This time, though, we go on to send a renderBufferStorage:fromDrawable: message to the context. This associates our renderbuffer, which is where we draw the stuff we want the user to see, to the iPhone screen. By sending this message, we’re able to display the images we create by sending a presentRenderbuffer: message later, once we’ve finished rendering our scene into this renderbuffer.

The glGetRenderbufferParameteriv() calls are used to request information from OpenGL. In this case, since we bound our renderbuffer to the iPhone screen, we can now get information about how big that screen is. We load our framebufferWidth and framebufferHeight instance variables with the width and height of the iPhone’s screen.

By the way, you may have noticed that ‘parameteriv’ is not a word. OpenGL has a special naming convention for many of its functions that pass data around. The actual name of the function is glGetRenderbufferParameter, and the ‘iv’ stuck on the end means that it uses an integer value (i) that’s passed by reference (v). You’ll notice places where the same base function name is used, but the last couple of characters will change based on what types of variables are being passed around.

        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

This call simply associates, or attaches, the renderbuffer to the framebuffer.

        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }

Finally, a little bit of error checking so we can write out to the log if anything went wrong.

After setting the context and framebuffer, remember that the awakeFromNib method in the EDCubeDemoViewController class did one other thing:

    if ([context API] == kEAGLRenderingAPIOpenGLES2)
        [self loadShaders];

If we are an OpenGL ES 2.0 application, we also need to load our shaders, the little programs that OpenGL runs at render time to draw our graphics the way we want them.

First, we’ll look at what the loadShaders message accomplishes, then we’ll talk about shaders for a few minutes.

- (BOOL)loadShaders
{
    GLuint vertShader, fragShader;
    NSString *vertShaderPathname, *fragShaderPathname;
   
    // Create shader program.
    program = glCreateProgram();
   
    // Create and compile vertex shader.
    vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname])
    {
        NSLog(@"Failed to compile vertex shader");
        return FALSE;
    }
   
    // Create and compile fragment shader.
    fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname])
    {
        NSLog(@"Failed to compile fragment shader");
        return FALSE;
    }
   
    // Attach vertex shader to program.
    glAttachShader(program, vertShader);
   
    // Attach fragment shader to program.
    glAttachShader(program, fragShader);
   
    // Bind attribute locations.
    // This needs to be done prior to linking.
    glBindAttribLocation(program, ATTRIB_VERTEX, "position");
    glBindAttribLocation(program, ATTRIB_COLOR, "color");
   
    // Link program.
    if (![self linkProgram:program])
    {
        NSLog(@"Failed to link program: %d", program);
       
        if (vertShader)
        {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader)
        {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (program)
        {
            glDeleteProgram(program);
            program = 0;
        }
       
        return FALSE;
    }
   
    // Get uniform locations.
    uniforms[UNIFORM_TRANSLATE] = glGetUniformLocation(program, "translate");
   
    // Release vertex and fragment shaders.
    if (vertShader)
        glDeleteShader(vertShader);
    if (fragShader)
        glDeleteShader(fragShader);
   
    return TRUE;
}

Wow.

    GLuint vertShader, fragShader;
    NSString *vertShaderPathname, *fragShaderPathname;
   
    // Create shader program.
    program = glCreateProgram();

This first part simply defines a few working variables and calls glCreateProgram(). The glCreateProgram() function really doesn’t do much more than create an empty program object that we attach our shaders to, then link to the current context.

    // Create and compile vertex shader.
    vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname])
    {
        NSLog(@"Failed to compile vertex shader");
        return FALSE;
    }
   
    // Create and compile fragment shader.
    fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname])
    {
        NSLog(@"Failed to compile fragment shader");
        return FALSE;
    }

Here we’re using some iOS classes and methods to load the shader code from their files, Shader.fsh and Shader.vsh. In some OpenGL implementations, you might see the shaders coded inline as strings, or included as pre-compiles binaries.

In our tutorial, we’ll keep them just like they are.

The pathForResource:ofType: message returns the location of the file we want to compile, which we pass along with the compileShader:type:file: message below it. We’ll look at that method in a bit.

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

The first compile process was for the vertex shader, Shader.vsh, and the second compile process is for the fragment shader, Shader.fsh. Don’t worry, we’ll talk about those soon, too.

    // Attach vertex shader to program.
    glAttachShader(program, vertShader);
   
    // Attach fragment shader to program.
    glAttachShader(program, fragShader);

Once compiled, the shaders are attached to the empty program we created earlier.

    // Bind attribute locations.
    // This needs to be done prior to linking.
    glBindAttribLocation(program, ATTRIB_VERTEX, "position");
    glBindAttribLocation(program, ATTRIB_COLOR, "color");

This requires a bit of explanation. Attributes are basically variables that you specify in your vertex shader. They do not work in your fragment shader.

Let’s take a quick look at our vertex shader, Shader.vsh.

attribute vec4 position;
attribute vec4 color;

varying vec4 colorVarying;

uniform float translate;

void main()
{
    gl_Position = position;
    gl_Position.y += sin(translate) / 2.0;

    colorVarying = color;
}

See those two variables at the top, labelled as attributes? The glBindAttributeLocation() function simply associates them, by name, to a specified index. In this case, ATTRIB_VERTEX is 0 and ATTRIB_COLOR is 1, so the attribute ‘position’ in the vertex shader can now be referenced from our code through index 0, or ATTRIB_VERTEX.

Similarly, the attribute ‘color’ in the vertex shader can now be referenced from our code through index 1, or ATTRIB_COLOR.

The vec4 is a special OpenGL type which means this is a vector type that has four values – basically an array with four members.

The ‘varying’ type is like a local work variable, except that if you have a ‘varying’ type of the same name in your fragment shader, this value is passed on from the vertex shader into the fragment shader.

Finally, the ‘uniform’ type is like a global, its value doesn’t change during the render process.

For the sake of completeness, let’s take a quick look at the fragment shader while we’re here.

varying lowp vec4 colorVarying;

void main()
{
    gl_FragColor = colorVarying;
}

There’s the colorVarying value defined as a varying lowp vec4 type. You know that a varying is like a local work variable, but one that can be passed from the vector shader to the fragment shader, and you know that vec4 is a four member array, but what’s a lowp? That just means ‘low precision’. There’s also a highp and a mediump, but we’ll worry about those later.

Now, back to the compile code.

    // Link program.
    if (![self linkProgram:program])
    {
        NSLog(@"Failed to link program: %d", program);
       
        if (vertShader)
        {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader)
        {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (program)
        {
            glDeleteProgram(program);
            program = 0;
        }
       
        return FALSE;
    }

We try to link the program, to which we’ve already attached the compiled shaders.

If something fails, we clean up and return FALSE. The code’s doing pretty much what you think it is, deleting anything still allocated after the failure.

    // Get uniform locations.
    uniforms[UNIFORM_TRANSLATE] = glGetUniformLocation(program, "translate");

Wait, what? Why didn’t we do this when we associated the attributes?

A uniform value is different from an attribute value. An attribute value is defined by us, and for each time the vertex shader runs, a unique piece of vertex data will be in those attribute values (more on that later). A uniform value, however, is allocated by the shaders at compile time, and we need to ask for its name.

In the case of attributes, we said ‘make this one zero and make this one one,’ but for the uniforms, OpenGL gets to decide the name and gives it back to us to use later, when we ask for it after the compile. That’s why our attributes are simple number values (ATTRIB_VERTEX = 0 and ATTRIB_COLOR = 1), but our uniforms are stored in an array of integers indexed by our uniform names.

Since UNIFORM_TRANSLATE = 0, uniform[0] will be set to the return value from glGetUniformLocation().

    // Release vertex and fragment shaders.
    if (vertShader)
        glDeleteShader(vertShader);
    if (fragShader)
        glDeleteShader(fragShader);
   
    return TRUE;

Finally, we release the memory used to store our shaders. Once they’re compiled and linked, we don’t need them anymore.

But wait, we sent messages to ourself to compile and link those shaders too, what’s going on with that?

Let’s see what we did in the compileShader:type:file: method.

- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
    GLint status;
    const GLchar *source;
   
    source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
    if (!source)
    {
        NSLog(@"Failed to load vertex shader");
        return FALSE;
    }
   
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
   
#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        NSLog(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif
   
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0)
    {
        glDeleteShader(*shader);
        return FALSE;
    }
   
    return TRUE;
}

Most of this is error checking and debug logging.

    GLint status;
    const GLchar *source;
   
    source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
    if (!source)
    {
        NSLog(@"Failed to load vertex shader");
        return FALSE;
    }

Remember when we called this method, we passed in the location of the shader we wanted to compile? The first line of code after the variable declarations reads the contents of that file in as a UTF-8 string, which is the format OpenGL will be expecting the shader data to be in.

If the file read fails, we’ll log it and return FALSE.

    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);

This code creates an empty shader, similar to the code that created the empty program earlier, then associates the UTF-8 string we just created with that empty shader.

No longer empty, the shader is compiled.

#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        NSLog(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif

At this point, if debugging is turned on (by a #define DEBUG 1 statement at the top of your program), we’ll write a shader compile log. First we have to ask the shader for the length of its compile log, then allocate the memory space to hold it. The glGetShaderInfoLog() function retrieves the compile log text, the NSLog() call writes it out, and the free() function deallocates the memory we were using to hold that log.

This code should not be executed outside of a test environment, since it will slow down processing, that’s why it’s wrapped in the #if defined(DEBUG) code.

    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0)
    {
        glDeleteShader(*shader);
        return FALSE;
    }
   
    return TRUE;

Finally, we check the compile status. If we get a FALSE (0), we delete the broken shader and return FALSE, otherwise all is good.

So that’s the compile step, but there was a link method too, right?

Yes, it looks like this.

- (BOOL)linkProgram:(GLuint)prog
{
    GLint status;
   
    glLinkProgram(prog);
   
#if defined(DEBUG)
    GLint logLength;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program link log:\n%s", log);
        free(log);
    }
#endif
   
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0)
        return FALSE;
   
    return TRUE;
}

If you’ve made it this far, then I know you don’t need this one broken down.

The glLinkProgram() function does the magic, and everything else is logging and error checking.

And that’s the end of the awakeFromNib processing in our EDCubeDemoViewController class. We now have an OpenGL ES 2.0 environment all set up and ready to go.

By the way, during our awakeFromNib processing, when we sent the EAGLView object the setContext: message, the EAGLView object was sent an initWithCoder: message as a part of initialization that looks like this.

- (id)initWithCoder:(NSCoder*)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
       
        eaglLayer.opaque = TRUE;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
                                        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
                                        nil];
    }
   
    return self;
}

Not too much going on here, just some configuration.

    self = [super initWithCoder:coder];
    if (self) {
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

Here, we send the initWithCoder: message to our parent, and if that works we enter a processing block.

We get a reference to our own CAEAGLLayer and put in in the local eaglLayer variable.

        eaglLayer.opaque = TRUE;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
                                        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
                                        nil];
    }
   
    return self;

Using that local variable, we set a few options.

Apple recommends that opaque always be set to TRUE for performance reasons. If the layer you’re drawing onto isn’t opaque, the graphics processor has to work a lot harder to blend in whatever might be behind it. If it’s opaque, the graphics processor can simply ignore whatever might be back there.

The drawableProperties variable takes an array of values, or more specifically, an NSDictionary object loaded with key value pairs.

In this case, we pass in a value of FALSE for the key kEAGLDrawablePropertyRetainedBacking and a value of kEAGLColorFormatRGB8 for the key kEAGLDrawablePropertyColorFormat. The nil value terminates the list of options.

In case you’re wondering, setting kEAGLDrawablePropertyRetainedBacking to FALSE means that whatever we draw will not persist, and setting kEAGLDrawablePropertyColorFormat to kEAGLColorFormatRGB8 specifies a 32-bit RGBA color format.

At the end of the method, we return a reference to ourself.

And after all of this processing in EDCubeDemoViewController and EAGLView, our OpenGL ES 2.0 environment is ready to go.

Part Three | Index | Part Five