Part IV - The iPhone OpenGL ES XCode Template Dissected - The Remaineder

In the last installment we spent a lot of time going into the drawView method.  We learned how OpenGL ES uses various matrices to control how things are drawn as well as the things that are drawn.   We saw that the standard template in XCode has some interesting things about it, such as rotating the view around the square rather than rotating the square itself. 


In this installment we'll go through the remainder of the EAGLView.m file.  Let's get started.


The next method after drawView is one called "layoutSubviews".


- (void)layoutSubviews {
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
    [self drawView];
}

This method overrides the standard layoutSubviews method.  It sets the current OpenGL ES context to our context, and then it calls the destroy and createFramebuffer methods that we saw are declared as private to this class.  We then invoke the drawView method we dissected last time.    Basically, we are making sure our view is initialized properly since we don't really layout any subviews here.

The next method creates the frameBuffer.

- (BOOL)createFramebuffer {
    
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);

These two lines return a number (in this case 1) of buffer names of the appropriate type (frame and render).  This is something I'm still trying to wrap my mind around.  OpenGL ES doesn't give you a buffer object, but rather a name associated with a buffer object.  That's what these two lines do.  The next two lines:

    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);

actually bind (associate) an actual buffer to the name returned.   I'm not sure why OpenGL requires two steps when one call could give you a name and bind it to a buffer of the appropriate time.  It seems a bit obtuse to me.

 [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];

This method actually allocates the buffer storage for our view (context).  In all of these methods we see "_OES" which is the OpenGL ES specific versions.  So in this line GL_RENDERBUFFER_OES allocates an OpenGL ES version of a render buffer.

  glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);

This takes our renderbuffer and attaches it to our framebuffer. So we allocated the two buffers, and we associate them together.  Again, the framebuffer is memory, while the renderbuffer is the memory that actually gets drawn and related to the hardware graphics memory.  Think of this as mapping a chunk of memory that the rendering (drawing) hardware will use to hold our drawing.  More interesting, the parameter GL_COLOR_ATTACHMENT0_OES is used to attach a color texture to our rendering buffer.  In this code think of this as mapping the two buffers together and assigning a color palette to those buffers--the OES one.

  glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
  

The backingWidth and backingHeight are actually retrieved by glGetRenderbufferParameterivOES for our renderbuffer.  Remember we used these in our viewPort method as part of the glViewport call.

Next we get the same set of logic with a check:

  if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }

USE_DEPTH_BUFFER is set to 0 by a define up top:

#define USE_DEPTH_BUFFER 0

If we rerun this with a value of 1 nothing seems to change. This snippet of code allocates a third buffer (a depthRenderbuffer) and pretty much does the same thing as the code we just went through.  So what is a depthRenderbuffer?  A depth render buffer is used to manage such things as shadows, alpha blending, etc.--basically things associated with drawing our object but not the object itself.

No render buffer:

With a depth render buffer.


No obvious changes in the standard XCode template.

Finally, this method checks for a successful creation of our buffer:

    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
    
    return YES;

glCheckFramebufferStatusOES checks the completness of the buffer's creation.  The completeness is measured based on whether all of the required attributes for a framebuffer have been defined.  This call returns information on the attributes that are wrong or missing if it doesn't return GL_FRAMEBUFFER_COMPLETE_OES.  Throughout this installment we've been talking about something I've been calling a framebuffer.  More specifically, OpenGL calls this a "frame buffer object" or FBO.  It is more than just a buffer full of memory, but that's a major part.  It represents a real object that includes the buffer as well as images, textures, colors, etc.  The glCheckFramebufferStatusOES call checks the completeness or "sanity" of the object in terms of how drawable it is.  It's a good idea to check this prior to trying to render this buffer.  If the framebuffer is created correctly we return true, otherwise false.

So now the idea of getting a name and attaching things to it makes a bit more sense. We aren't dealing with just a buffer, but a buffer object. We get a name (basically the type of FBO we want) then we bind various properties to it one at a time.  So it's more complicated than just combining two calls into one.  The question may still be, why can't we just use setters to do all this to our FBO?  The answer comes down to the fact that OpenGL is procedural "c" and although it has this thing known as an FBO, it's not an object in the OOP sense, so we have to set its properties via multiple procedural calls.

The next method:

- (void)destroyFramebuffer {
    
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
    
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}

is pretty straightforward.  It deletes our frame and render buffers.  It optionally destroys the depthRenderbuffer as well if our define is set.  These are all 'c' based routines so we don't need to [x release], although setting our buffers to 0 makes sure we are in a known state in case we need to check.

Next,
- (void)startAnimation {
    self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}


is pure Cocoa.  We create an NSTimer object that repeats every "animationInterval" periods (that was set to 1/60th of a second in our initWithCoder method). Every 1/60th of a second we invoke drawView to render our frame.

This method is followed by:

- (void)stopAnimation {
    self.animationTimer = nil;
}

This frees our timer stopping the periodic call to drawView and is invoked by the next method.

- (void)setAnimationInterval:(NSTimeInterval)interval {
    
    animationInterval = interval;
    if (animationTimer) {
        [self stopAnimation];
        [self startAnimation];
    }
}

This is a simple setter method for animationInterval.  As part of setting the interval we stop and free any old timer, and create a new timer with the new interval.  So actually setting the animationInterval value in initWithCoder causes the animation to begin.

Finally, we haver our dealloc method which stops the animation and releases our context after detaching it from our OpenGL ES environment by setting it to nil.  Again because OpenGL ES is "c" based, we need to free any memory allocated using "c' functions--we didn't allocate any so there is nothing to do in the dealloc of this sample application.


- (void)dealloc {
    
    [self stopAnimation];
    
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
    
    [context release];  
    [super dealloc];
}

That takes care of the EAGLView.m file.  We've learned how to set up our buffers and verify they are correct and complete, and got our mind around FBOs.  We saw how to draw the image, and how to control the animation.  We have a couple more minor helper methods to go through that are part of the delegate. We'll tackle those next time.