Beginning OpenGL ES 2.0 with GLKit Part 1

A tutorial to get you up-to-speed with the basics of using OpenGL with GLKit, even if you have no experience whatsoever. By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Introducing GLKViewController

You know that code we just wrote in that last section? Well you can just forget about it, because there's a much easier way to do so by using GLKViewController :]

The reason I showed you how to do it with plain GLKView first was so you understand the point behind using GLKViewController - it saves you from writing that code, plus adds some extra neat features that you would have had to code yourself.

So try out GLKViewController. Modify your application:didFinishLaunchingWithOptions to look like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    GLKView *view = [[GLKView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    view.context = context;
    view.delegate = self;
    //[self.window addSubview:view];
    
    _increasing = YES;
    _curRed = 0.0;
    
    //view.enableSetNeedsDisplay = NO;
    //CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
    //[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    
    GLKViewController * viewController = [[GLKViewController alloc] initWithNibName:nil bundle:nil]; // 1
    viewController.view = view; // 2
    viewController.delegate = self; // 3
    viewController.preferredFramesPerSecond = 60; // 4
    self.window.rootViewController = viewController; // 5
        
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Feel free to delete the commented lines - I just commented them out so it is easy to see what's no longer needed. There are also four new lines (marked with comments):

1. Create a GLKViewController. This creates a new instance of a GLKViewController programatically. In this case, it has no XIB associated.

2. Set the GLKViewController's view. The root view of a GLKViewController should be a GLKView, so we set it to the one we already created.

3. Set the GLKViewController's delegate. We set the current class (AppDelegate) as the delegate of the GLKViewController. This means that the GLKViewController will notify us each frame so we can run game logic, or when the game pauses (a nice built-in feature of GLKViewController we'll demonstrate later).

4. Set the preferred FPS. The GLKViewController will call your draw method a certain number of times per second. This number gives a hint to the GLKViewController how often you'd like to be called. Of course, if your game takes a long time to render frames, the actual number may be lower than this.

The default value is 30 FPS. Apple's guidelines are to set this to whatever your app can reliably support to the frame rate is consistent and doesn't seem to stutter. This app is very simple so can easily run at 60 FPS, so we set it to that.

Also as an FYI, if you want to see the actual number of times the OS will attempt to call your update/draw methods, check the read-only framesPerSecond property.

5. Set the rootViewController. We want this view controller to be the first thing that shows up, so we add it as the rootViewController of the window. Note that we no longer need to add the view as a subview of the window manually, because it's the root view of the GLKViewController.

Notice that we no longer need the code to run the render loop and tell the GLView to refresh each frame - GLKViewController does that for us in the background! So go ahead and comment out the render method as well.

Also remember that we set the GLKViewController's delegate to the current class (AppDelegate), so let's mark it as implementing GLKViewControllerDelegate. Switch to AppDelegate.h and replace the @implementation with the following line:

@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate, GLKViewControllerDelegate>

The final step is to update the glkView:drawInRect method, and add the implementation for GLKViewController's glkViewControllerUpdate callback:

#pragma mark - GLKViewDelegate

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
           
    glClearColor(_curRed, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
}

#pragma mark - GLKViewControllerDelegate

- (void)glkViewControllerUpdate:(GLKViewController *)controller {
    if (_increasing) {
        _curRed += 1.0 * controller.timeSinceLastUpdate;
    } else {
        _curRed -= 1.0 * controller.timeSinceLastUpdate;
    }
    if (_curRed >= 1.0) {
        _curRed = 1.0;
        _increasing = NO;
    }
    if (_curRed <= 0.0) {
        _curRed = 0.0;
        _increasing = YES;
    }
}

Note that we moved the code to change the current color from the draw method (where it didn't really belong) to the update method (intended for game/app logic).

Also notice that we changed the amount the red color increments from a hardcoded value to a calculated value, based on the amount of time since the last update. This is nice because it guarantees the animation will always proceed at the same speed, regardless of the frame rate.

This is another of those convenient things GLKViewController does for you! We didn't have to write special code to store the time since the last update - it did it for us! There are some other time-based properties, but we'll discuss those later.

GLKViewController and Storyboards

So far, we've manually created the GLKViewController and GLKView because it was a simple way to introduce you to how they work. But you probably wouldn't want to do it this way in a real app - it's much better to leverage the power of Storyboards, so you can include this view controller anywhere you want in your app hierarchy!

So let's do a little refactoring to accomplish that. First, let's create a subclass of GLKViewController that we can use to contain our app's logic. So create a new file with the iOS\Cocoa Touch\UIViewController subclass template, name the class HelloGLKitViewController, as a subclass of GLKViewController (you can type this in even though it's not in the dropdown). Make sure Targeted for iPad and With XIB for user interface are both unselected, and create the file.

Open up HelloGLKitViewController.m, and start by adding a private category on the class to contain the instance variables we need, and a new property to store the context:

@interface HelloGLKitViewController () {
    float _curRed;
    BOOL _increasing;
   
}
@property (strong, nonatomic) EAGLContext *context;

@end

@implementation HelloGLKitViewController 
@synthesize context = _context;

Then implement viewDidLoad and viewDidUnload as the following:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }

    GLKView *view = (GLKView *)self.view;
    view.context = self.context;
}

- (void)viewDidUnload
{
    [super viewDidUnload];

    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    self.context = nil;
}

In viewDidLoad, we create an OpenGL ES 2.0 context (same as we did last time in the App Delegate) and squirrel it away. Our root view is a GLKView (we know this because we set it up this way in the Storyboard editor), so we cast it as one. We then set its context to the OpenGL context we just created.

Note that we don't have to set the view controller as the view's delegate - GLKViewController does this automatically behind the scenes.

In viewDidUnload, we just do the opposite to clean up. We have to make sure there's no references left to our context, so we check to see if the current context is our context, and set it to nil if so. We also clear out or reference to it.

At the bottom of the file, add the implementations of the glkView:drawInRect and update callbacks, similar to before:

#pragma mark - GLKViewDelegate

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    
    glClearColor(_curRed, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
}

#pragma mark - GLKViewControllerDelegate

- (void)update {
    if (_increasing) {
        _curRed += 1.0 * self.timeSinceLastUpdate;
    } else {
        _curRed -= 1.0 * self.timeSinceLastUpdate;
    }
    if (_curRed >= 1.0) {
        _curRed = 1.0;
        _increasing = NO;
    }
    if (_curRed <= 0.0) {
        _curRed = 0.0;
        _increasing = YES;
    }
}

Note that the update method is called plain "update", because now that we're inside the GLKViewCotroller we can just override this method instead of having to set a delegate. Also the timeSinceLastUpdate is access via "self", not a passed in view controller.

With this in place, let's create the Storyboard. Create a new file with the iOS\User Interface\Storyboard template, choose iPhone for the device family, and save it as MainStoryboard.storyboard.

Open MainStoryboard.storyboard, and from the Objects panel drag a View Controller into the grid area. Select the View Controller, and in the Identity Inspector set the class to HelloGLKitViewController:

Setting the class of the View Controller

Also, select the View inside the View Controller, and in the Identity Inspector set the Class to GLKView.

To make this Storyboard run on startup, open HelloGLKit-Info.plist, control-click in the blank area, and select Add Row. From the dropdown select Main storyboard file base name, and enter MainStoryboard.

That pretty much completes everything we need, but we still have some old code in AppDelegate that we need to clean up. Start by deleting the _curRed and _increasing instance variables from AppDelegate.m. Also delete the glkView:drawInRect and glkViewControllerUpdate methods.

And delete pretty much everything from application:didFinishLaunchingWithOptions so it looks like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    return YES;
}

And modify the AppDelegate @interface to strip out the two GLKit delegates since we aren't using those anymore:

@interface AppDelegate : UIResponder <UIApplicationDelegate>

That's it! Compile and run your app, and you'll see the "red alert" effect working as usual.

At this point, you're getting pretty close to the setup you get when you choose the OpenGL Game template with the Storyboard option set (except it has a lot of other code in there you can just delete if you don't need it). Feel free to choose that in the future when you're creating a new OpenGL project to save a little time - but now you know how it works from the ground up!