In the first part of this I went through the default XCode Template Header that is created when you start an iPhone OpenGL ES project. In this session, I'll work through the code in the actual implementation of that OpenGL ES-specific UIView. Because I don't want to be overwhelmed--or overwhelm you I'll take it one method at a time. So in this installment I'll work on the first real method initWithCoder, but first the preliminaries.
The main things I take away from the header is that OpenGL ES uses various buffers to do the drawing and animation, we always draw into a "context", and we need to make sure we deal with OpenGL ES on its own terms, by using OpenGL ES datatypes rather than those of c or Objective-C, since they seem to be platform independent.
Ok, lets dive into the implementation EAGLView.m.
The first thing to note are the imports:
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
#import "EAGLView.h"
OpenGL ES not only requires its own header, EAGLDrawable.h, but the Quartz header as well. If you're new to Mac or iPhone development, Quartz is the imaging (drawing) framework used by these platforms. So OpenGL ES doesn't replace the fundamental drawing capabilities of the iPhone (or Mac), but rather adds a layer of capability on top of it. That is to say, it seems you can't create a pure OpenGL ES application. This makes sense, since Quartz is used to handle the platform specific UI widgets and layer drawing. And you can't really have an application without these.
We of course also import the header we went through in the first part.
What follows next is:
// A class extension to declare private methods
@interface EAGLView ()
@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;
- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;
@end
As the comment points out this is the typical way of extending the interface (header) to declare private methods. The main thing that happens here is that we declare the attributes for the OpenGL ES context, and the timer we discussed last time. This is done using the @property command. Two additional private methods are declare in addition to the three public methods in the header. The two appear complimentary, in that they manage the lifecycle of a framebuffer. This again makes sense, since it would be bad design to have one UIView try to create buffers used by another UIView. In this template project there is only one view, but still it's a good idea to have the view manage its own buffers. Isolation is a good thing, as is cohesion.
Finally we get to the start of the implementation:
@implementation EAGLView
@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;
// You must implement this method
+ (Class)layerClass {
NSLog([[CAEAGLLayer class]description]);
return [CAEAGLLayer class];
}
This is a class method that returns the class of CAEAGLLayer. If you remember, this is a wrapper around our drawing environment. So what does this return? It returns a class CAEAGLLayer. The comment says that you "must implment this". What happens if you don't? If you take out this method and try to run the application, it throws an exception:
*** -[CALayer setDrawableProperties:]: unrecognized selector sent to instance 0x5242b0
Digging through the docs tells me that CAEAGLLayer conforms to the EAGLDrawable protocol which says that our CAEAGLLayer can be used to draw on and be displayed by an EAGLContext. We have one of those defined in our interface. So when we draw to our context, it actually does the display on our layer. The setDrawableProperties is part of the EAGLDrawable protocol. By not implementing this peculiar method, we break the protocol. I'll work on figuring out when and how it gets called later.
Next is the init method the real meat of this article.
//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {
if ((self = [super initWithCoder:coder])) {
A quick peek at the xib file doesn't really show anything OpenGL specific other than the fact that the main window is a subclass of us--which makes sense. We'll get into the xib later.
We finally see something new:
// Get the layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
The self.layer returns the generic UIView's CoreAnimation layer. This is the layer that is used for drawing things on the view. This is a drawing surface used by the iPhone. We cast this to a CAEAGLLayer, so we can deal with the drawing layer as if it were an OpenGL ES drawing layer instead of a CoreAnimation one.
eaglLayer.opaque = YES;
Then makes the layer opaque. Interesting in that this is done to the underlying UIView, not to the coerced OpenGL ES layer. This doesn't really do anything obvious in the template. If you comment this line out and rerun the app it looks identical. You probably want to set the layer to be opaque in the event you want your view to be a subview on top of something else.
The next line:
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
handles the EAGLDrawable protocol by setting the drawableProperties which is a dictionary that describes the properties of the drawing surface (our layer.) This is defined as a dictionary. So it seems OpenGL ES allows us to specify how the background looks/acts in addition to the actual thing we are drawing. That is to say, we not only have to worry about drawing a square (or something), but have to also worry about drawing the background properly by setting its drawableProperties.
There are two pairs in this dictionary. The first key: kEAGLDrawablePropertyRetainedBacking is set to 'false'. What kEAGLDrawablePropertyRetainedBacking does is determine whether the surface retains whatever is drawn on it. The way I understand this is when you draw on the surface (layer) the image drawn is not treated as an object that can be refreshed by the surface, but rather as a phantom image that either the code has to refresh, or is subject to erasure. A simpler way to put it is the layer won't autorefresh its contents. The documentation states this is the preferred and default setting. That's my interpretation--let me know if I'm mistaken. The second key: kEAGLDrawablePropertyColorFormat is a little easier to understand. This sets the internal color buffer for the layer. The template sets this to: kEAGLColorFormatRGBA8 which is a 16 bit RGB with Alpha. (This amounts to 2 to the 16th power if you didn't know--a ton of color choices.) This is the default value In fact this is the default dictionary for drawableProperties. What is interesting, is if you comment out this line and rerun the application, the color scheme of the cube seems to change--it has more white. I'm not sure why.
So that takes care of setting up our drawing layer. We now need to deal with the context.
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
So this creates our OpenGL ES drawing context, and specifies that it uses the OpenGL ES 1.0 API. kEAGLRenderingAPIOpenGLES1 is the only option available for the iPhone as of 2.2. In a more general sense, I guess you can create an OpenGL context, and it conforms to a given OpenGL API.
The next snippet is important.
if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}
This checks to make sure we got a valid context object, and sets the current OpenGL ES context to be the context we just created. If we can't set the context we exit. Why is that? Well from our dissection of the header, we found out that we can only draw into a context, so if we can't get one or set it to be the current context, we can't draw anything, so we may as well quit.
animationInterval = 1.0 / 60.0;
Sets the animation interval (frame rate) to 1/60 of a second, so fps is 60. That's very good for animation.
That's the end of the initWithCoder method. Next time we'll tackle the drawView method.

