Chapter 1. Quick-Start Guide

Rumors of my assimilation are greatly exaggerated.

Captain Picard, Star Trek: First Contact

In this chapter, you’ll plunge in and develop your first application from scratch. The goal is to write a HelloArrow program that draws an arrow and rotates it in response to an orientation change.

You’ll be using the OpenGL ES API to render the arrow, but OpenGL is only one of many graphics technologies supported on the iPhone. At first, it can be confusing which of these technologies is most appropriate for your requirements. It’s also not always obvious which technologies are iPhone-specific and which cross over into general Mac OS X development.

Apple neatly organizes all of the iPhone’s public APIs into four layers: Cocoa Touch, Media Services, Core Services, and Core OS. Mac OS X is a bit more sprawling, but it too can be roughly organized into four layers, as shown in Figure 1-1.

Mac OS X and iPhone programming stacks
Figure 1-1. Mac OS X and iPhone programming stacks

At the very bottom layer, Mac OS X and the iPhone share their kernel architecture and core operating system; these shared components are collectively known as Darwin.

Despite the similarities between the two platforms, they diverge quite a bit in their handling of OpenGL. Figure 1-1 includes some OpenGL-related classes, shown in bold. The NSOpenGLView class in Mac OS X does not exist on the iPhone, and the iPhone’s EAGLContext and CAEGLLayer classes are absent on Mac OS X. The OpenGL API itself is also quite different in the two platforms, because Mac OS X supports full-blown OpenGL while the iPhone relies on the more svelte OpenGL ES.

The iPhone graphics technologies include the following:

Quartz 2D rendering engine

Vector-based graphics library that supports alpha blending, layers, and anti-aliasing. This is also available on Mac OS X. Applications that leverage Quartz technology must reference a framework (Apple’s term for a bundle of resources and libraries) known as Quartz Core.

Core Graphics

Vanilla C interface to Quartz. This is also available on Mac OS X.

UIKit

Native windowing framework for iPhone. Among other things, UIKit wraps Quartz primitives into Objective-C classes. This has a Mac OS X counterpart called AppKit, which is a component of Cocoa.

Cocoa Touch

Conceptual layer in the iPhone programming stack that contains UIKit along with a few other frameworks.

Core Animation

Objective-C framework that facilitates complex animations.

OpenGL ES

Low-level hardware-accelerated C API for rendering 2D or 3D graphics.

EAGL

Tiny glue API between OpenGL ES and UIKit. Some EAGL classes (such as CAEGLLayer) are defined in Quartz Core framework, while others (such as EAGLContext) are defined in the OpenGL ES framework.

This book chiefly deals with OpenGL ES, the only technology in the previous list that isn’t Apple-specific. The OpenGL ES specification is controlled by a consortium of companies called the Khronos Group. Different implementations of OpenGL ES all support the same core API, making it easy to write portable code. Vendors can pick and choose from a formally defined set of extensions to the API, and the iPhone supports a rich set of these extensions. We’ll cover many of these extensions throughout this book.

Transitioning to Apple Technology

Yes, you do need a Mac to develop applications for the iPhone App Store! Developers with a PC background should quell their fear; my own experience was that the PC-to-Apple transition was quite painless, aside from some initial frustration with a different keyboard.

Xcode serves as Apple’s preferred development environment for Mac OS X. If you are new to Xcode, it might initially strike you as resembling an email client more than an IDE. This layout is actually quite intuitive; after learning the keyboard shortcuts, I found Xcode to be a productive environment. It’s also fun to work with. For example, after typing in a closing delimiter such as ), the corresponding ( momentarily glows and seems to push itself out from the screen. This effect is pleasant and subtle; the only thing missing is a kitten-purr sound effect. Maybe Apple will add that to the next version of Xcode.

Objective-C

Now we come to the elephant in the room. At some point, you’ve probably heard that Objective-C is a requirement for iPhone development. You can actually use pure C or C++ for much of your application logic, if it does not make extensive use of UIKit. This is especially true for OpenGL development because it is a C API. Most of this book uses C++; Objective-C is used only for the bridge code between the iPhone operating system and OpenGL ES.

The origin of Apple’s usage of Objective-C lies with NeXT, which was another Steve Jobs company whose technology was ahead of its time in many ways—perhaps too far ahead. NeXT failed to survive on its own, and Apple purchased it in 1997. To this day, you can still find the NS prefix in many of Apple’s APIs, including those for the iPhone.

Some would say that Objective-C is not as complex or feature-rich as C++, which isn’t necessarily a bad thing. In many cases, Objective-C is the right tool for the right job. It’s a fairly simple superset of C, making it quite easy to learn.

However, for 3D graphics, I find that certain C++ features are indispensable. Operator overloading makes it possible to perform vector math in a syntactically natural way. Templates allow the reuse of vector and matrix types using a variety of underlying numerical representations. Most importantly, C++ is widely used on many platforms, and in many ways, it’s the lingua franca of game developers.

A Brief History of OpenGL ES

In 1982, a Stanford University professor named Jim Clark started one of the world’s first computer graphics companies: Silicon Graphics Computer Systems, later known as SGI. SGI engineers needed a standard way of specifying common 3D transformations and operations, so they designed a proprietary API called IrisGL. In the early 1990s, SGI reworked IrisGL and released it to the public as an industry standard, and OpenGL was born.

Over the years, graphics technology advanced even more rapidly than Moore’s law could have predicted.[1] OpenGL went through many revisions while largely preserving backward compatibility. Many developers believed that the API became bloated. When the mobile phone revolution took off, the need for a trimmed-down version of OpenGL became more apparent than ever. The Khronos Group announced OpenGL for Embedded Systems (OpenGL ES) at the annual SIGGRAPH conference in 2003.

OpenGL ES rapidly gained popularity and today is used on many platforms besides the iPhone, including Android, Symbian, and PlayStation 3.

All Apple devices support at least version 1.1 of the OpenGL ES API, which added several powerful features to the core specification, including vertex buffer objects and mandatory multitexture support, both of which you’ll learn about in this book.

In March 2007, the Khronos Group released the OpenGL ES 2.0 specification, which entailed a major break in backward compatibility by ripping out many of the fixed-function features and replacing them with a shading language. This new model for controlling graphics simplified the API and shifted greater control into the hands of developers. Many developers (including myself) find the ES 2.0 programming model to be more elegant than the ES 1.1 model. But in the end, the two APIs simply represent two different approaches to the same problem. With ES 2.0, an application developer needs to do much more work just to write a simple Hello World application. The 1.x flavor of OpenGL ES will probably continue to be used for some time, because of its low implementation burden.

Choosing the Appropriate Version of OpenGL ES

Apple’s newer handheld devices, such as the iPhone 3GS and iPad, have graphics hardware that supports both ES 1.1 and 2.0; these devices are said to have a programmable graphics pipeline because the graphics processor executes instructions rather than performing fixed mathematical operations. Older devices like the first-generation iPod touch, iPhone, and iPhone 3G are said to have a fixed-function graphics pipeline because they support only ES 1.1.

Before writing your first line of code, be sure to have a good handle on your graphics requirements. It’s tempting to use the latest and greatest API, but keep in mind that there are many 1.1 devices out there, so this could open up a much broader market for your application. It can also be less work to write an ES 1.1 application, if your graphical requirements are fairly modest.

Of course, many advanced effects are possible only in ES 2.0—and, as I mentioned, I believe it to be a more elegant programming model.

To summarize, you can choose from among four possibilities for your application:

  • Use OpenGL ES 1.1 only.

  • Use OpenGL ES 2.0 only.

  • Determine capabilities at runtime; use ES 2.0 if it’s supported; otherwise, fall back to ES 1.1.

  • Release two separate applications: one for ES 1.1, one for ES 2.0. (This could get messy.)

Choose wisely! We’ll be using the third choice for many of the samples in this book, including the HelloArrow sample presented in this chapter.

Getting Started

Assuming you already have a Mac, the first step is to head over to Apple’s iPhone developer site and download the SDK. With only the free SDK in hand, you have the tools at your disposal to develop complex applications and even test them on the iPhone Simulator.

The iPhone Simulator cannot emulate certain features such as the accelerometer, nor does it perfectly reflect the iPhone’s implementation of OpenGL ES. For example, a physical iPhone cannot render anti-aliased lines using OpenGL’s smooth lines feature, but the simulator can. Conversely, there may be extensions that a physical iPhone supports that the simulator does not. (Incidentally, we’ll discuss how to work around the anti-aliasing limitation later in this book.)

Having said all that, you do not need to own an iPhone to use this book. I’ve ensured that every code sample either runs against the simulator or at least fails gracefully in the rare case where it leverages a feature not supported on the simulator.

If you do own an iPhone and are willing to cough up a reasonable fee ($100 at the time of this writing), you can join Apple’s iPhone Developer Program to enable deployment to a physical iPhone. When I did this, it was not a painful process, and Apple granted me approval almost immediately. If the approval process takes longer in your case, I suggest forging ahead with the simulator while you wait. I actually use the simulator for most of my day-to-day development anyway, since it provides a much faster debug-build-run turnaround than deploying to my device.

The remainder of this chapter is written in a tutorial style. Be aware that some of the steps may vary slightly from what’s written, depending on the versions of the tools that you’re using. These minor deviations most likely pertain to specific actions within the Xcode UI; for example, a menu might be renamed or shifted in future versions. However, the actual sample code is relatively future-proof.

Installing the iPhone SDK

You can download the iPhone SDK from here:

http://developer.apple.com/iphone/

It’s packaged as a .dmg file, Apple’s standard disk image format. After you download it, it should automatically open in a Finder window—if it doesn’t, you can find its disk icon on the desktop and open it from there. The contents of the disk image usually consist of an “about” PDF, a Packages subfolder, and an installation package, whose icon resembles a cardboard box. Open the installer and follow the steps. When confirming the subset of components to install, simply accept the defaults. When you’re done, you can “eject” the disk image to remove it from the desktop.

As an Apple developer, Xcode will become your home base. I recommend dragging it to your Dock at the bottom of the screen. You’ll find Xcode in /Developer/Applications/Xcode.

Tip

If you’re coming from a PC background, Mac’s windowing system may seem difficult to organize at first. I highly recommend the Exposé and Spaces desktop managers that are built into Mac OS X. Exposé lets you switch between windows using an intuitive “spread-out” view. Spaces can be used to organize your windows into multiple virtual desktops. I’ve used several virtual desktop managers on Windows, and in my opinion Spaces beats them all hands down.

Building the OpenGL Template Application with Xcode

When running Xcode for the first time, it presents you with a Welcome to Xcode dialog. Click the Create a new Xcode project button. (If you have the welcome dialog turned off, go to FileNew Project.) Now you’ll be presented with the dialog shown in Figure 1-2, consisting of a collection of templates. The template we’re interested in is OpenGL ES Application, under the iPhone OS heading. It’s nothing fancy, but it is a fully functional OpenGL application and serves as a decent starting point.

New Project dialog
Figure 1-2. New Project dialog

In the next dialog, choose a goofy name for your application, and then you’ll finally see Xcode’s main window. Build and run the application by selecting Build and Run from the Build menu or by pressing ⌘-Return. When the build finishes, you should see the iPhone Simulator pop up with a moving square in the middle, as in Figure 1-3. When you’re done gawking at this amazing application, press ⌘-Q to quit.

OpenGL ES Application template
Figure 1-3. OpenGL ES Application template

Deploying to Your Real iPhone

This is not required for development, but if you want to deploy your application to a physical device, you should sign up for Apple’s iPhone Developer Program. This enables you to provision your iPhone for developer builds, in addition to the usual software you get from the App Store. Provisioning is a somewhat laborious process, but thankfully it needs to be done only once per device. Apple has now made it reasonably straightforward by providing a Provisioning Assistant applet that walks you through the process. You’ll find this applet after logging into the iPhone Dev Center and entering the iPhone Developer Program Portal.

When your iPhone has been provisioned properly, you should be able to see it in Xcode’s Organizer window (Control-⌘-O). Open up the Provisioning Profiles tree node in the left pane, and make sure your device is listed.

Now you can go back to Xcode’s main window, open the Overview combo box in the upper-left corner, and choose the latest SDK that has the Device prefix. The next time you build and run (⌘-Return), the moving square should appear on your iPhone.

HelloArrow with Fixed Function

In the previous section, you learned your way around the development environment with Apple’s boilerplate OpenGL application, but to get a good understanding of the fundamentals, you need to start from scratch. This section of the book builds a simple application from the ground up using OpenGL ES 1.1. The 1.x track of OpenGL ES is sometimes called fixed-function to distinguish it from the OpenGL ES 2.0 track, which relies on shaders. We’ll learn how to modify the sample to use shaders later in the chapter.

Let’s come up with a variation of the classic Hello World in a way that fits well with the theme of this book. As you’ll learn later, most of what gets rendered in OpenGL can be reduced to triangles. We can use two overlapping triangles to draw a simple arrow shape, as shown in Figure 1-4. Any resemblance to the Star Trek logo is purely coincidental.

Arrow shape composed from two triangles
Figure 1-4. Arrow shape composed from two triangles

To add an interesting twist, the program will make the arrow stay upright when the user changes the orientation of his iPhone.

Layering Your 3D Application

If you love Objective-C, then by all means, use it everywhere you can. This book supports cross-platform code reuse, so we leverage Objective-C only when necessary. Figure 1-5 depicts a couple ways of organizing your application code such that the guts of the program are written in C++ (or vanilla C), while the iPhone-specific glue is written in Objective-C. The variation on the right separates the application engine (also known as game logic) from the rendering engine. Some of the more complex samples in this book take this approach.

Layered 3D iPhone applications
Figure 1-5. Layered 3D iPhone applications

The key to either approach depicted in Figure 1-6 is designing a robust interface to the rendering engine and ensuring that any platform can use it. The sample code in this book uses the name IRenderingEngine for this interface, but you can call it what you want.

A cross-platform OpenGL ES application
Figure 1-6. A cross-platform OpenGL ES application

The IRenderingEngine interface also allows you to build multiple rendering engines into your application, as shown in Figure 1-7. This facilitates the “Use ES 2.0 if supported, otherwise fall back” scenario mentioned in Choosing the Appropriate Version of OpenGL ES. We’ll take this approach for HelloArrow.

An iPhone application that supports ES 1.1 and 2.0
Figure 1-7. An iPhone application that supports ES 1.1 and 2.0

You’ll learn more about the pieces in Figure 1-7 as we walk through the code to HelloArrow. To summarize, you’ll be writing three classes:

RenderingEngine1 and RenderingEngine2 (portable C++)

These classes are where most of the work takes place; all calls to OpenGL ES are made from here. RenderingEngine1 uses ES 1.1, while RenderingEngine2 uses ES 2.0.

HelloArrowAppDelegate (Objective-C)

Small Objective-C class that derives from NSObject and adopts the UIApplicationDelegate protocol. (“Adopting a protocol” in Objective-C is somewhat analogous to “implementing an interface” in languages such as Java or C#.) This does not use OpenGL or EAGL; it simply initializes the GLView object and releases memory when the application closes.

GLView (Objective-C)

Derives from the standard UIView class and uses EAGL to instance a valid rendering surface for OpenGL.

Starting from Scratch

Launch Xcode and start with the simplest project template by going to FileNew Project and selecting Window-Based Application from the list of iPhone OS application templates. Name it HelloArrow.

Xcode comes bundled with an application called Interface Builder, which is Apple’s interactive designer for building interfaces with UIKit (and AppKit on Mac OS X). I don’t attempt to cover UIKit because most 3D applications do not make extensive use of it. For best performance, Apple advises against mixing UIKit with OpenGL.

Note

For simple 3D applications that aren’t too demanding, it probably won’t hurt you to add some UIKit controls to your OpenGL view. We cover this briefly in Mixing OpenGL ES and UIKit.

Linking in the OpenGL and Quartz Libraries

In the world of Apple programming, you can think of a framework as being similar to a library, but technically it’s a bundle of resources. A bundle is a special type of folder that acts like a single file, and it’s quite common in Mac OS X. For example, applications are usually deployed as bundles—open the action menu on nearly any icon in your Applications folder, and you’ll see an option for Show Package Contents, which allows you to get past the façade.

You need to add some framework references to your Xcode project. Pull down the action menu for the Frameworks folder. This can be done by selecting the folder and clicking the Action icon or by right-clicking or Control-clicking the folder. Next choose AddExisting Frameworks. Select OpenGLES.Framework, and click the Add button. You may see a dialog after this; if so, simply accept its defaults. Now, repeat this procedure with QuartzCore.Framework.

Note

Why do we need Quartz if we’re writing an OpenGL ES application? The answer is that Quartz owns the layer object that gets presented to the screen, even though we’re rendering with OpenGL. The layer object is an instance of CAEGLLayer, which is a subclass of CALayer; these classes are defined in the Quartz Core framework.

Subclassing UIView

The abstract UIView class controls a rectangular area of the screen, handles user events, and sometimes serves as a container for child views. Almost all standard controls such as buttons, sliders, and text fields are descendants of UIView. We tend to avoid using these controls in this book; for most of the sample code, the UI requirements are so modest that OpenGL itself can be used to render simple buttons and various widgets.

For our HelloArrow sample, we do need to define a single UIView subclass, since all rendering on the iPhone must take place within a view. Select the Classes folder in Xcode, click the Action icon in the toolbar, and select AddNew file. Under the Cocoa Touch Class category, select the Objective-C class template, and choose UIView in the Subclass of menu. In the next dialog, name it GLView.mm, and leave the box checked to ensure that the corresponding header gets generated. The .mm extension indicates that this file can support C++ in addition to Objective-C. Open GLView.h. You should see something like this:

#import <UIKit/UIKit.h>

@interface GLView : UIView {
}

@end

For C/C++ veterans, this syntax can be a little jarring—just wait until you see the syntax for methods! Fear not, it’s easy to become accustomed to.

#import is almost the same thing as #include but automatically ensures that the header file does not get expanded twice within the same source file. This is similar to the #pragma once feature found in many C/C++ compilers.

Keywords specific to Objective-C stand out because of the @ prefix. The @interface keyword marks the beginning of a class declaration; the @end keyword marks the end of a class declaration. A single source file may contain several class declarations and therefore can have several @interface blocks.

As you probably already guessed, the previous code snippet simply declares an empty class called GLView that derives from UIView. What’s less obvious is that data fields will go inside the curly braces, while method declarations will go between the ending curly brace and the @end, like this:

#import <UIKit/UIKit.h>

@interface GLView : UIView {
    // Protected fields go here...
}    

// Public methods go here...

@end

By default, data fields have protected accessibility, but you can make them private using the @private keyword. Let’s march onward and fill in the pieces shown in bold in Example 1-1. We’re also adding some new #imports for OpenGL-related stuff.

Example 1-1. GLView class declaration
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

@interface GLView : UIView {
    EAGLContext* m_context;
}

 - (void) drawView;

@end

The m_context field is a pointer to the EAGL object that manages our OpenGL context. EAGL is a small Apple-specific API that links the iPhone operating system with OpenGL.

Note

Every time you modify API state through an OpenGL function call, you do so within a context. For a given thread running on your system, only one context can be current at any time. With the iPhone, you’ll rarely need more than one context for your application. Because of the limited resources on mobile devices, I don’t recommend using multiple contexts.

If you have a C/C++ background, the drawView method declaration in Example 1-1 may look odd. It’s less jarring if you’re familiar with UML syntax, but UML uses - and + to denote private and public methods, respectively; with Objective-C, - and + denote instance methods and class methods. (Class methods in Objective-C are somewhat similar to C++ static methods, but in Objective-C, the class itself is a proper object.)

Take a look at the top of the GLView.mm file that Xcode generated. Everything between @implementation and @end is the definition of the GLView class. Xcode created three methods for you: initWithFrame, drawRect (which may be commented out), and dealloc. Note that these methods do not have declarations in the header file that Xcode generated. In this respect, an Objective-C method is similar to a plain old function in C; it needs a forward declaration only if gets called before it’s defined. I usually declare all methods in the header file anyway to be consistent with C++ class declarations.

Take a closer look at the first method in the file:

- (id) initWithFrame: (CGRect) frame
{
   if (self = [super initWithFrame:frame]) {
       // Initialize code...
   }
   return self;
}

This is an Objective-C initializer method, which is somewhat analogous to a C++ constructor. The return type and argument types are enclosed in parentheses, similar to C-style casting syntax. The conditional in the if statement accomplishes several things at once: it calls the base implementation of initWithFrame, assigns the object’s pointer to self, and checks the result for success.

In Objective-C parlance, you don’t call methods on objects; you send messages to objects. The square bracket syntax denotes a message. Rather than a comma-separated list of values, arguments are denoted with a whitespace-separated list of name-value pairs. The idea is that messages can vaguely resemble English sentences. For example, consider this statement, which adds an element to an NSMutableDictionary:

[myDictionary setValue: 30 forKey: @"age"];

If you read the argument list aloud, you get an English sentence! Well, sort of.

That’s enough of an Objective-C lesson for now. Let’s get back to the HelloArrow application. In GLView.mm, provide the implementation to the layerClass method by adding the following snippet after the @implementation line:

+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

This simply overrides the default implementation of layerClass to return an OpenGL-friendly layer type. The class method is similar to the typeof operator found in other languages; it returns an object that represents the type itself, rather than an instance of the type.

Note

The + prefix means that this is an override of a class method rather than an instance method. This type of override is a feature of Objective-C rarely found in other languages.

Now, go back to initWithFrame, and replace the contents of the if block with some EAGL initialization code, as shown in Example 1-2.

Example 1-2. EAGL initialization in GLView.mm
- (id) initWithFrame: (CGRect) frame
{
    if (self = [super initWithFrame:frame]) {
        CAEAGLLayer* eaglLayer = (CAEAGLLayer*) super.layer; 1
        eaglLayer.opaque = YES; 2

        m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; 3

        if (!m_context || ![EAGLContext setCurrentContext:m_context]) { 4
            [self release];
            return nil;5
        }

        // Initialize code...
    }
    return self;
}

Here’s what’s going on:

1

Retrieve the layer property from the base class (UIView), and downcast it from CALayer into a CAEAGLLayer. This is safe because of the override to the layerClass method.

2

Set the opaque property on the layer to indicate that you do not need Quartz to handle transparency. This is a performance benefit that Apple recommends in all OpenGL programs. Don’t worry, you can easily use OpenGL to handle alpha blending.

3

Create an EAGLContext object, and tell it which version of OpenGL you need, which is ES 1.1.

4

Tell the EAGLContext to make itself current, which means any subsequent OpenGL calls in this thread will be tied to it.

5

If context creation fails or if setCurrentContext fails, then poop out and return nil.

Next, continue filling in the initialization code with some OpenGL setup. Replace the OpenGL Initialization comment with Example 1-3.

Example 1-3. OpenGL initialization in GLView.mm
GLuint framebuffer, renderbuffer;
glGenFramebuffersOES(1, &framebuffer);
glGenRenderbuffersOES(1, &renderbuffer);

glBindFramebufferOES(GL_FRAMEBUFFER_OES, framebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, renderbuffer);

[m_context
    renderbufferStorage:GL_RENDERBUFFER_OES
    fromDrawable: eaglLayer];

glFramebufferRenderbufferOES(
    GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
    GL_RENDERBUFFER_OES, renderbuffer);

glViewport(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));

[self drawView];

Example 1-3 starts off by generating two OpenGL identifiers, one for a renderbuffer and one for a framebuffer. Briefly, a renderbuffer is a 2D surface filled with some type of data (in this case, color), and a framebuffer is a bundle of renderbuffers. You’ll learn more about framebuffer objects (FBOs) in later chapters.

Note

The use of FBOs is an advanced feature that is not part of the core OpenGL ES 1.1 API, but it is specified in an OpenGL extension that all iPhones support. In OpenGL ES 2.0, FBOs are included in the core API. It may seem odd to use this advanced feature in the simple HelloArrow program, but all OpenGL iPhone applications need to leverage FBOs to draw anything to the screen.

The renderbuffer and framebuffer are both of type GLuint, which is the type that OpenGL uses to represent various objects that it manages. You could just as easily use unsigned int in lieu of GLuint, but I recommend using the GL-prefixed types for objects that get passed to the API. If nothing else, the GL-prefixed types make it easier for humans to identify which pieces of your code interact with OpenGL.

After generating identifiers for the framebuffer and renderbuffer, Example 1-3 then binds these objects to the pipeline. When an object is bound, it can be modified or consumed by subsequent OpenGL operations. After binding the renderbuffer, storage is allocated by sending the renderbufferStorage message to the EAGLContext object.

Note

For an off-screen surface, you would use the OpenGL command glRenderbufferStorage to perform allocation, but in this case you’re associating the renderbuffer with an EAGL layer. You’ll learn more about off-screen surfaces later in this book.

Next, the glFramebufferRenderbufferOES command is used to attach the renderbuffer object to the framebuffer object.

After this, the glViewport command is issued. You can think of this as setting up a coordinate system. In Chapter 2 you’ll learn more precisely what’s going on here.

The final call in Example 1-3 is to the drawView method. Go ahead and create the drawView implementation:

- (void) drawView
{
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    [m_context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

This uses OpenGL’s “clear” mechanism to fill the buffer with a solid color. First the color is set to gray using four values (red, green, blue, alpha). Then, the clear operation is issued. Finally, the EAGLContext object is told to present the renderbuffer to the screen. Rather than drawing directly to the screen, most OpenGL programs render to a buffer that is then presented to the screen in an atomic operation, just like we’re doing here.

You can remove the drawRect stub that Xcode provided for you. The drawRect method is typically used for a “paint refresh” in more traditional UIKit-based applications; in 3D applications, you’ll want finer control over when rendering occurs.

At this point, you almost have a fully functioning OpenGL ES program, but there’s one more loose end to tie up. You need to clean up when the GLView object is destroyed. Replace the definition of dealloc with the following:

- (void) dealloc
{
    if ([EAGLContext currentContext] == m_context)
        [EAGLContext setCurrentContext:nil];

    [m_context release];  
    [super dealloc];
}

You can now build and run the program, but you won’t even see the gray background color just yet. This brings us to the next step: hooking up the application delegate.

Hooking Up the Application Delegate

The application delegate template (HelloArrowAppDelegate.h) that Xcode provided contains nothing more than an instance of UIWindow. Let’s add a pointer to an instance of the GLView class along with a couple method declarations (new/changed lines are shown in bold):

#import <UIKit/UIKit.h>
#import "GLView.h"

@interface HelloArrowAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow* m_window;
    GLView* m_view;
}

@property (nonatomic, retain) IBOutlet UIWindow *m_window;

@end

If you performed the instructions in Optional: Creating a Clean Slate, you won’t see the @property line, which is fine. Interface Builder leverages Objective-C’s property mechanism to establish connections between objects, but we’re not using Interface Builder or properties in this book. In brief, the @property keyword declares a property; the @synthesize keyword defines accessor methods.

Note that the Xcode template already had a window member, but I renamed it to m_window. This is in keeping with the coding conventions that we use throughout this book.

Note

I recommend using Xcode’s Refactor feature to rename this variable because it will also rename the corresponding property (if it exists). Simply right-click the window variable and choose Refactor. If you did not make the changes shown in Optional: Creating a Clean Slate, you must use Refactor so that the xib file knows the window is now represented by m_window.

Now open the corresponding HelloArrowAppDelegate.m file. Xcode already provided skeleton implementations for applicationDidFinishLaunching and dealloc as part of the Window-Based Application template that we selected to create our project.

Note

Since you need this file to handle both Objective-C and C++, you must rename the extension to .mm. Right-click the file to bring up the action menu, and then select Rename.

Flesh out the file as shown in Example 1-4.

Example 1-4. HelloArrowAppDelegate.mm
#import "HelloArrowAppDelegate.h"
#import <UIKit/UIKit.h>
#import "GLView.h"

@implementation HelloArrowAppDelegate

- (BOOL) application: (UIApplication*) application
         didFinishLaunchingWithOptions: (NSDictionary*) launchOptions    
{
    CGRect screenBounds = [[UIScreen mainScreen] bounds];

    m_window = [[UIWindow alloc] initWithFrame: screenBounds];
    m_view = [[GLView alloc] initWithFrame: screenBounds];

    [m_window addSubview: m_view];
    [m_window makeKeyAndVisible];
    return YES;
}

- (void) dealloc
{
    [m_view release];
    [m_window release];
    [super dealloc];
}

@end

Example 1-4 uses the alloc-init pattern to construct the window and view objects, passing in the bounding rectangle for the entire screen.

If you haven’t removed the Interface Builder bits as described in Optional: Creating a Clean Slate, you’ll need to make a couple changes to the previous code listing:

  • Add a new line after @implementation:

    @synthesize m_window;

    As mentioned previously, the @synthesize keyword defines a set of property accessors, and Interface Builder uses properties to hook things up.

  • Remove the line that constructs m_window. Interface Builder has a special way of constructing the window behind the scenes. (Leave in the calls to makeKeyAndVisible and release.)

Compile and build, and you should now see a solid gray screen. Hooray!

Setting Up the Icons and Launch Image

To set a custom launch icon for your application, create a 57×57 PNG file (72×72 for the iPad), and add it to your Xcode project in the Resources folder. If you refer to a PNG file that is not in the same location as your project folder, Xcode will copy it for you; be sure to check the box labeled “Copy items into destination group’s folder (if needed)” before you click Add. Then, open the HelloArrow-Info.plist file (also in the Resources folder), find the Icon file property, and enter the name of your PNG file.

The iPhone will automatically give your icon rounded corners and a shiny overlay. If you want to turn this feature off, find the HelloArrow-Info.plist file in your Xcode project, select the last row, click the + button, and choose Icon already includes gloss and bevel effects from the menu. Don’t do this unless you’re really sure of yourself; Apple wants users to have a consistent look in SpringBoard (the built-in program used to launch apps).

In addition to the 57×57 launch icon, Apple recommends that you also provide a 29×29 miniature icon for the Spotlight search and Settings screen. The procedure is similar except that the filename must be Icon-Small.png, and there’s no need to modify the .plist file.

For the splash screen, the procedure is similar to the small icon, except that the filename must be Default.png and there’s no need to modify the .plist file. The iPhone fills the entire screen with your image, so the ideal size is 320×480, unless you want to see an ugly stretchy effect. Apple’s guidelines say that this image isn’t a splash screen at all but a “launch image” whose purpose is to create a swift and seamless startup experience. Rather than showing a creative logo, Apple wants your launch image to mimic the starting screen of your running application. Of course, many applications ignore this rule!

Dealing with the Status Bar

Even though your application fills the renderbuffer with gray, the iPhone’s status bar still appears at the top of the screen. One way of dealing with this would be adding the following line to didFinishLaunchingWithOptions:

[application setStatusBarHidden: YES withAnimation: UIStatusBarAnimationNone];

The problem with this approach is that the status bar does not hide until after the splash screen animation. For HelloArrow, let’s remove the pesky status bar from the very beginning. Find the HelloArrowInfo.plist file in your Xcode project, and add a new property by selecting the last row, clicking the + button, choosing “Status bar is initially hidden” from the menu, and checking the box.

Of course, for some applications, you’ll want to keep the status bar visible—after all, the user might want to keep an eye on battery life and connectivity status! If your application has a black background, you can add a Status bar style property and select the black style. For nonblack backgrounds, the semitransparent style often works well.

Defining and Consuming the Rendering Engine Interface

At this point, you have a walking skeleton for HelloArrow, but you still don’t have the rendering layer depicted in Figure 1-7. Add a file to your Xcode project to define the C++ interface. Right-click the Classes folder, and choose AddNew file, select C and C++, and choose Header File. Call it IRenderingEngine.hpp. The .hpp extension signals that this is a pure C++ file; no Objective-C syntax is allowed.[2] Replace the contents of this file with Example 1-5.

Example 1-5. IRenderingEngine.hpp
// Physical orientation of a handheld device, equivalent to UIDeviceOrientation.
enum DeviceOrientation {
    DeviceOrientationUnknown,
    DeviceOrientationPortrait,
    DeviceOrientationPortraitUpsideDown,
    DeviceOrientationLandscapeLeft,
    DeviceOrientationLandscapeRight,
    DeviceOrientationFaceUp,
    DeviceOrientationFaceDown,
};

// Creates an instance of the renderer and sets up various OpenGL state.
struct IRenderingEngine* CreateRenderer1();

// Interface to the OpenGL ES renderer; consumed by GLView.
struct IRenderingEngine {
    virtual void Initialize(int width, int height) = 0;    
    virtual void Render() const = 0;
    virtual void UpdateAnimation(float timeStep) = 0;
    virtual void OnRotate(DeviceOrientation newOrientation) = 0;
    virtual ~IRenderingEngine() {}
};

It seems redundant to include an enumeration for device orientation when one already exists in an iPhone header (namely, UIDevice.h), but this makes the IRenderingEngine interface portable to other environments.

Since the view class consumes the rendering engine interface, you need to add an IRenderingEngine pointer to the GLView class declaration, along with some fields and methods to help with rotation and animation. Example 1-6 shows the complete class declaration. New fields and methods are shown in bold. Note that we removed the two OpenGL ES 1.1 #imports; these OpenGL calls are moving to the RenderingEngine1 class. The EAGL header is not part of the OpenGL standard, but it’s required to create the OpenGL ES context.

Example 1-6. GLView.h
#import "IRenderingEngine.hpp"
#import <OpenGLES/EAGL.h>
#import <QuartzCore/QuartzCore.h>

@interface GLView : UIView {
@private
    EAGLContext* m_context;
    IRenderingEngine* m_renderingEngine;
    float m_timestamp;
}

- (void) drawView: (CADisplayLink*) displayLink;
- (void) didRotate: (NSNotification*) notification;

@end

Example 1-7 is the full listing for the class implementation. Calls to the rendering engine are highlighted in bold. Note that GLView no longer contains any OpenGL calls; we’re delegating all OpenGL work to the rendering engine.

Example 1-7. GLView.mm
#import <OpenGLES/EAGLDrawable.h>
#import "GLView.h"
#import "mach/mach_time.h"
#import <OpenGLES/ES2/gl.h> // <-- for GL_RENDERBUFFER only

@implementation GLView

+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

- (id) initWithFrame: (CGRect) frame
{
    if (self = [super initWithFrame:frame]) {
        CAEAGLLayer* eaglLayer = (CAEAGLLayer*) super.layer;
        eaglLayer.opaque = YES;

        m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

        if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
            [self release];
            return nil;
        }

        m_renderingEngine = CreateRenderer1();        

        [m_context
            renderbufferStorage:GL_RENDERBUFFER
            fromDrawable: eaglLayer];
        
        m_renderingEngine->Initialize(CGRectGetWidth(frame), CGRectGetHeight(frame));

        [self drawView: nil];
        m_timestamp = CACurrentMediaTime();

        CADisplayLink* displayLink;
        displayLink = [CADisplayLink displayLinkWithTarget:self
                                     selector:@selector(drawView:)];
        
        [displayLink addToRunLoop:[NSRunLoop currentRunLoop]
                     forMode:NSDefaultRunLoopMode];

        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
        
        [[NSNotificationCenter defaultCenter]
            addObserver:self
            selector:@selector(didRotate:)
            name:UIDeviceOrientationDidChangeNotification
            object:nil];
    }
    return self;
}

- (void) didRotate: (NSNotification*) notification
{
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    m_renderingEngine->OnRotate((DeviceOrientation) orientation);
    [self drawView: nil];
}

- (void) drawView: (CADisplayLink*) displayLink
{
    if (displayLink != nil) {
        float elapsedSeconds = displayLink.timestamp - m_timestamp;
        m_timestamp = displayLink.timestamp;
        m_renderingEngine->UpdateAnimation(elapsedSeconds);
    }

    m_renderingEngine->Render();
    [m_context presentRenderbuffer:GL_RENDERBUFFER];
}

@end

This completes the Objective-C portion of the project, but it won’t build yet because you still need to implement the rendering engine. There’s no need to dissect all the code in Example 1-7, but a brief summary follows:

  • The initWithFrame method calls the factory method to instantiate the C++ renderer. It also sets up two event handlers. One is for the “display link,” which fires every time the screen refreshes. The other event handler responds to orientation changes.

  • The didRotate event handler casts the iPhone-specific UIDeviceOrientation to our portable DeviceOrientation type and then passes it on to the rendering engine.

  • The drawView method, called in response to a display link event, computes the elapsed time since it was last called and passes that value into the renderer’s UpdateAnimation method. This allows the renderer to update any animations or physics that it might be controlling.

  • The drawView method also issues the Render command and presents the renderbuffer to the screen.

Note

At the time of writing, Apple recommends CADisplayLink for triggering OpenGL rendering. An alternative strategy is leveraging the NSTimer class. CADisplayLink became available with iPhone OS 3.1, so if you need to support older versions of the iPhone OS, take a look at NSTimer in the documentation.

Implementing the Rendering Engine

In this section, you’ll create an implementation class for the IRenderingEngine interface. Right-click the Classes folder, choose AddNew file, click the C and C++ category, and select the C++ File template. Call it RenderingEngine1.cpp, and deselect the “Also create RenderingEngine1.h” option, since you’ll declare the class directly within the .cpp file. Enter the class declaration and factory method shown in Example 1-8.

Example 1-8. RenderingEngine1 class and factory method
#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#include "IRenderingEngine.hpp"

class RenderingEngine1 : public IRenderingEngine {
public:
    RenderingEngine1();
    void Initialize(int width, int height);
    void Render() const;
    void UpdateAnimation(float timeStep) {}
    void OnRotate(DeviceOrientation newOrientation) {}
private:
    GLuint m_framebuffer;
    GLuint m_renderbuffer;
};

IRenderingEngine* CreateRenderer1()
{
    return new RenderingEngine1();
}

For now, UpdateAnimation and OnRotate are implemented with stubs; you’ll add support for the rotation feature after we get up and running.

Example 1-9 shows more of the code from RenderingEngine1.cpp with the OpenGL initialization code.

Example 1-9. Vertex data and RenderingEngine construction
struct Vertex {
    float Position[2];
    float Color[4];
};

// Define the positions and colors of two triangles.
const Vertex Vertices[] = {
    {{-0.5, -0.866}, {1, 1, 0.5f, 1}},
    {{0.5, -0.866},  {1, 1, 0.5f, 1}},
    {{0, 1},         {1, 1, 0.5f, 1}},
    {{-0.5, -0.866}, {0.5f, 0.5f, 0.5f}},
    {{0.5, -0.866},  {0.5f, 0.5f, 0.5f}},
    {{0, -0.4f},     {0.5f, 0.5f, 0.5f}},
};

RenderingEngine1::RenderingEngine1()
{
    glGenRenderbuffersOES(1, &m_renderbuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffer);
}

void RenderingEngine1::Initialize(int width, int height)
{
    // Create the framebuffer object and attach the color buffer.
    glGenFramebuffersOES(1, &m_framebuffer);
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffer);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
                                 GL_COLOR_ATTACHMENT0_OES,
                                 GL_RENDERBUFFER_OES,
                                 m_renderbuffer);
    
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);

    // Initialize the projection matrix.
    const float maxX = 2;
    const float maxY = 3;
    glOrthof(-maxX, +maxX, -maxY, +maxY, -1, 1);

    glMatrixMode(GL_MODELVIEW);
}

Example 1-9 first defines a POD type (plain old data) that represents the structure of each vertex that makes up the triangles. As you’ll learn in the chapters to come, a vertex in OpenGL can be associated with a variety of attributes. HelloArrow requires only two attributes: a 2D position and an RGBA color.

In more complex OpenGL applications, the vertex data is usually read from an external file or generated on the fly. In this case, the geometry is so simple that the vertex data is defined within the code itself. Two triangles are specified using six vertices. The first triangle is yellow, the second gray (see Figure 1-4, shown earlier).

Next, Example 1-9 divides up some framebuffer initialization work between the constructor and the Initialize method. Between instancing the rendering engine and calling Initialize, the caller (GLView) is responsible for allocating the renderbuffer’s storage. Allocation of the renderbuffer isn’t done with the rendering engine because it requires Objective-C.

Last but not least, Initialize sets up the viewport transform and projection matrix. The projection matrix defines the 3D volume that contains the visible portion of the scene. This will be explained in detail in the next chapter.

To recap, here’s the startup sequence:

  1. Generate an identifier for the renderbuffer, and bind it to the pipeline.

  2. Allocate the renderbuffer’s storage by associating it with an EAGL layer. This has to be done in the Objective-C layer.

  3. Create a framebuffer object, and attach the renderbuffer to it.

  4. Set up the vertex transformation state with glViewport and glOrthof.

Example 1-10 contains the implementation of the Render method.

Example 1-10. Initial Render implementation
void RenderingEngine1::Render() const
{
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT);1

    glEnableClientState(GL_VERTEX_ARRAY);2
    glEnableClientState(GL_COLOR_ARRAY);
    
    glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &Vertices[0].Position[0]);3
    glColorPointer(4, GL_FLOAT, sizeof(Vertex), &Vertices[0].Color[0]);

    GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex);
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);4
    
    glDisableClientState(GL_VERTEX_ARRAY);5
    glDisableClientState(GL_COLOR_ARRAY);
}

We’ll examine much of this in the next chapter, but briefly here’s what’s going on:

1

Clear the renderbuffer to gray.

2

Enable two vertex attributes (position and color).

3

Tell OpenGL how to fetch the data for the position and color attributes. We’ll examine these in detail later in the book; for now, see Figure 1-8.

4

Execute the draw command with glDrawArrays, specifying GL_TRIANGLES for the topology, 0 for the starting vertex, and vertexCount for the number of vertices. This function call marks the exact time that OpenGL fetches the data from the pointers specified in the preceding gl*Pointer calls; this is also when the triangles are actually rendered to the target surface.

5

Disable the two vertex attributes; they need to be enabled only during the preceding draw command. It’s bad form to leave attributes enabled because subsequent draw commands might want to use a completely different set of vertex attributes. In this case, we could get by without disabling them because the program is so simple, but it’s a good habit to follow.

Interleaved arrays
Figure 1-8. Interleaved arrays

Congratulations, you created a complete OpenGL program from scratch! Figure 1-9 shows the result.

HelloArrow!
Figure 1-9. HelloArrow!

Handling Device Orientation

Earlier in the chapter, I promised you would learn how to rotate the arrow in response to an orientation change. Since you already created the listener in the UIView class in Example 1-7, all that remains is handling it in the rendering engine.

First add a new floating-point field to the RenderingEngine class called m_currentAngle. This represents an angle in degrees, not radians. Note the changes to UpdateAnimation and OnRotate (they are no longer stubs and will be defined shortly).

class RenderingEngine1 : public IRenderingEngine {
public:
    RenderingEngine1();
    void Initialize(int width, int height);
    void Render() const;
    void UpdateAnimation(float timeStep);
    void OnRotate(DeviceOrientation newOrientation);
private:
    float m_currentAngle;
    GLuint m_framebuffer;
    GLuint m_renderbuffer;
};

Now let’s implement the OnRotate method as follows:

void RenderingEngine1::OnRotate(DeviceOrientation orientation)
{
    float angle = 0;
    
    switch (orientation) {
        case DeviceOrientationLandscapeLeft:
            angle = 270;
            break;

        case DeviceOrientationPortraitUpsideDown:
            angle = 180;
            break;

        case DeviceOrientationLandscapeRight:
            angle = 90;
            break;
    }
    
    m_currentAngle = angle;
}

Note that orientations such as Unknown, Portrait, FaceUp, and FaceDown are not included in the switch statement, so the angle defaults to zero in those cases.

Now you can rotate the arrow using a call to glRotatef in the Render method, as shown in Example 1-11. New code lines are shown in bold. This also adds some calls to glPushMatrix and glPopMatrix to prevent rotations from accumulating. You’ll learn more about these commands (including glRotatef) in the next chapter.

Example 1-11. Final Render implementation
void RenderingEngine1::Render() const
{
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    glPushMatrix();
    glRotatef(m_currentAngle, 0, 0, 1);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    
    glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &Vertices[0].Position[0]);
    glColorPointer(4, GL_FLOAT, sizeof(Vertex), &Vertices[0].Color[0]);
    
    GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex);
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);
    
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);

    glPopMatrix();
}

Animating the Rotation

You now have a HelloArrow program that rotates in response to an orientation change, but it’s lacking a bit of grace—most iPhone applications smoothly rotate the image, rather than suddenly jolting it by 90º.

It turns out that Apple provides infrastructure for smooth rotation via the UIViewController class, but this is not the recommended approach for OpenGL ES applications. There are several reasons for this:

  • For best performance, Apple recommends avoiding interaction between Core Animation and OpenGL ES.

  • Ideally, the renderbuffer stays the same size and aspect ratio for the lifetime of the application. This helps performance and simplifies code.

  • In graphically intense applications, developers need to have complete control over animations and rendering.

To achieve the animation effect, Example 1-12 adds a new floating-point field to the RenderingEngine class called m_desiredAngle. This represents the destination value of the current animation; if no animation is occurring, then m_currentAngle and m_desiredAngle are equal.

Example 1-12 also introduces a floating-point constant called RevolutionsPerSecond to represent angular velocity, and the private method RotationDirection, which I’ll explain later.

Example 1-12. Final RenderingEngine class declaration and constructor
#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#include "IRenderingEngine.hpp"

static const float RevolutionsPerSecond = 1;

class RenderingEngine1 : public IRenderingEngine {
public:
    RenderingEngine1();
    void Initialize(int width, int height);
    void Render() const;
    void UpdateAnimation(float timeStep);
    void OnRotate(DeviceOrientation newOrientation);
private:
    float RotationDirection() const;
    float m_desiredAngle;
    float m_currentAngle;
    GLuint m_framebuffer;
    GLuint m_renderbuffer;
};

...

void RenderingEngine1::Initialize(int width, int height)
{
    // Create the framebuffer object and attach the color buffer.
    glGenFramebuffersOES(1, &m_framebuffer);
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffer);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
                                 GL_COLOR_ATTACHMENT0_OES,
                                 GL_RENDERBUFFER_OES,
                                 m_renderbuffer);

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);

    // Initialize the projection matrix.
    const float maxX = 2;
    const float maxY = 3;
    glOrthof(-maxX, +maxX, -maxY, +maxY, -1, 1);

    glMatrixMode(GL_MODELVIEW);

    // Initialize the rotation animation state.
    OnRotate(DeviceOrientationPortrait);
    m_currentAngle = m_desiredAngle;
}

Now you can modify OnRotate so that it changes the desired angle rather than the current angle:

void RenderingEngine1::OnRotate(DeviceOrientation orientation)
{
    float angle = 0;
    
    switch (orientation) {
        ...
    }
    
    m_desiredAngle = angle;
}

Before implementing UpdateAnimation, think about how the application decides whether to rotate the arrow clockwise or counterclockwise. Simply checking whether the desired angle is greater than the current angle is incorrect; if the user changes his device from a 270º orientation to a 0º orientation, the angle should increase up to 360º.

This is where the RotationDirection method comes in. It returns –1, 0, or +1, depending on which direction the arrow needs to spin. Assume that m_currentAngle and m_desiredAngle are both normalized to values between 0 (inclusive) and 360 (exclusive).

float RenderingEngine1::RotationDirection() const
{
    float delta = m_desiredAngle - m_currentAngle;
    if (delta == 0)
        return 0;

    bool counterclockwise = ((delta > 0 && delta <= 180) || (delta < -180));
    return counterclockwise ? +1 : -1;
}

Now you’re ready to write the UpdateAnimation method, which takes a time step in seconds:

void RenderingEngine1::UpdateAnimation(float timeStep)
{
    float direction = RotationDirection();
    if (direction == 0)
        return;

    float degrees = timeStep * 360 * RevolutionsPerSecond;
    m_currentAngle += degrees * direction;

    // Ensure that the angle stays within [0, 360).
    if (m_currentAngle >= 360)
        m_currentAngle -= 360;
    else if (m_currentAngle < 0)
        m_currentAngle += 360;

    // If the rotation direction changed, then we overshot the desired angle.
    if (RotationDirection() != direction)
        m_currentAngle = m_desiredAngle;
}

This is fairly straightforward, but that last conditional might look curious. Since this method incrementally adjusts the angle with floating-point numbers, it could easily overshoot the destination, especially for large time steps. Those last two lines correct this by simply snapping the angle to the desired position. You’re not trying to emulate a shaky compass here, even though doing so might be a compelling iPhone application!

You now have a fully functional HelloArrow application. As with the other examples, you can find the complete code on this book’s website (see the preface for more information on code samples).

HelloArrow with Shaders

In this section you’ll create a new rendering engine that uses ES 2.0. This will show you the immense difference between ES 1.1 and 2.0. Personally I like the fact that Khronos decided against making ES 2.0 backward compatible with ES 1.1; the API is leaner and more elegant as a result.

Thanks to the layered architecture of HelloArrow, it’s easy to add ES 2.0 support while retaining 1.1 functionality for older devices. You’ll be making these four changes:

  1. Add some new source files to the project for the vertex shader and fragment shader.

  2. Update frameworks references if needed.

  3. Change the logic in GLView so that it attempts initWithAPI with ES 2.0.

  4. Create the new RenderingEngine2 class by modifying a copy of RenderingEngine1.

These changes are described in detail in the following sections. Note that step 4 is somewhat artificial; in the real world, you’ll probably want to write your ES 2.0 backend from the ground up.

Shaders

By far the most significant new feature in ES 2.0 is the shading language. Shaders are relatively small pieces of code that run on the graphics processor, and they are divided into two categories: vertex shaders and fragment shaders. Vertex shaders are used to transform the vertices that you submit with glDrawArrays, while fragment shaders compute the colors for every pixel in every triangle. Because of the highly parallel nature of graphics processors, thousands of shader instances execute simultaneously.

Shaders are written in a C-like language called OpenGL Shading Language (GLSL), but unlike C, you do not compile GLSL programs within Xcode. Shaders are compiled at runtime, on the iPhone itself. Your application submits shader source to the OpenGL API in the form of a C-style string, which OpenGL then compiles to machine code.

Note

Some implementations of OpenGL ES do allow you to compile your shaders offline; on these platforms your application submits binaries rather than strings at runtime. Currently, the iPhone supports shader compilation only at runtime. Its ARM processor compiles the shaders and sends the resulting machine code over to the graphics processor for execution. That little ARM does some heavy lifting!

The first step to upgrading HelloArrow is creating a new project folder for the shaders. Right-click the HelloArrow root node in the Groups & Files pane, and choose AddNew Group. Call the new group Shaders.

Next, right-click the Shaders folder, and choose AddNew file. Select the Empty File template in the Other category. Name it Simple.vert, and add /Shaders after HelloArrow in the location field. You can deselect the checkbox under Add To Targets, because there’s no need to deploy the file to the device. In the next dialog, allow it to create a new directory. Now repeat the procedure with a new file called Simple.frag.

Before showing you the code for these two files, let me explain a little trick. Rather than reading in the shaders with file I/O, you can simply embed them within your C/C++ code through the use of #include. Multiline strings are usually cumbersome in C/C++, but they can be tamed with a sneaky little macro:

#define STRINGIFY(A)  #A

Later in this section, we’ll place this macro definition at the top of the rendering engine source code, right before #including the shaders. The entire shader gets wrapped into a single string—without the use of quotation marks on every line!

Examples 1-13 and 1-14 show the contents of the vertex shader and fragment shader. For brevity’s sake, we’ll leave out the STRINGIFY accouterments in future shader listings, but we’re including them here for clarity.

Example 1-13. Simple.vert
const char* SimpleVertexShader = STRINGIFY(

attribute vec4 Position;
attribute vec4 SourceColor;
varying vec4 DestinationColor;
uniform mat4 Projection;
uniform mat4 Modelview;

void main(void)
{
    DestinationColor = SourceColor;
    gl_Position = Projection * Modelview * Position;
}
);

First the vertex shader declares a set of attributes, varyings, and uniforms. For now you can think of these as connection points between the vertex shader and the outside world. The vertex shader itself simply passes through a color and performs a standard transformation on the position. You’ll learn more about transformations in the next chapter. The fragment shader (Example 1-14) is even more trivial.

Example 1-14. Simple.frag
const char* SimpleFragmentShader = STRINGIFY(

varying lowp vec4 DestinationColor;

void main(void)
{
    gl_FragColor = DestinationColor;
}
);

Again, the varying parameter is a connection point. The fragment shader itself does nothing but pass on the color that it’s given.

Frameworks

Next, make sure all the frameworks in HelloArrow are referencing a 3.1 (or greater) version of the SDK. To find out which version a particular framework is using, right-click it in Xcode’s Groups & Files pane, and select Get Info to look at the full path.

Note

There’s a trick to quickly change all your SDK references by manually modifying the project file. First you need to quit Xcode. Next, open Finder, right-click HelloArrow.xcodeproj, and select Show package contents. Inside, you’ll find a file called project.pbxproj, which you can then open with TextEdit. Find the two places that define SDKROOT, and change them appropriately.

GLView

You might recall passing in a version constant when constructing the OpenGL context; this is another place that obviously needs to be changed. In the Classes folder, open GLView.mm, and change this snippet:

m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
    [self release];
    return nil;
}

m_renderingEngine = CreateRenderer1();

to this:

EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
m_context = [[EAGLContext alloc] initWithAPI:api];

if (!m_context || ForceES1) {
    api = kEAGLRenderingAPIOpenGLES1;
    m_context = [[EAGLContext alloc] initWithAPI:api];
}

if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
    [self release];
    return nil;
}

if (api == kEAGLRenderingAPIOpenGLES1) {
    m_renderingEngine = CreateRenderer1();
} else {
    m_renderingEngine = CreateRenderer2();
}

The previous code snippet creates a fallback path to allow the application to work on older devices while leveraging ES 2.0 on newer devices. For convenience, the ES 1.1 path is used even on newer devices if the ForceES1 constant is enabled. Add this to the top of GLView.mm:

const bool ForceES1 = false;

There’s no need to make any changes to the IRenderingEngine interface, but you do need to add a declaration for the new CreateRenderer2 factory method in IRenderingEngine.hpp:

...

// Create an instance of the renderer and set up various OpenGL state.
struct IRenderingEngine* CreateRenderer1();
struct IRenderingEngine* CreateRenderer2();

// Interface to the OpenGL ES renderer; consumed by GLView.
struct IRenderingEngine {
    virtual void Initialize(int width, int height) = 0;    
    virtual void Render() const = 0;
    virtual void UpdateAnimation(float timeStep) = 0;
    virtual void OnRotate(DeviceOrientation newOrientation) = 0;
    virtual ~IRenderingEngine() {}
};

RenderingEngine Implementation

You’re done with the requisite changes to the glue code; now it’s time for the meat. Use Finder to create a copy of RenderingEngine1.cpp (right-click or Control-click RenderingEngine1.cpp and choose Reveal in Finder), and name the new file RenderingEngine2.cpp. Add it to your Xcode project by right-clicking the Classes group and choosing AddExisting Files. Next, revamp the top part of the file as shown in Example 1-15. New or modified lines are shown in bold.

Example 1-15. RenderingEngine2 declaration
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#include <cmath>
#include <iostream>
#include "IRenderingEngine.hpp"

#define STRINGIFY(A)  #A
#include "../Shaders/Simple.vert"
#include "../Shaders/Simple.frag"

static const float RevolutionsPerSecond = 1;

class RenderingEngine2 : public IRenderingEngine {
public:
    RenderingEngine2();
    void Initialize(int width, int height);
    void Render() const;
    void UpdateAnimation(float timeStep);
    void OnRotate(DeviceOrientation newOrientation);
private:
    float RotationDirection() const;
    GLuint BuildShader(const char* source, GLenum shaderType) const;
    GLuint BuildProgram(const char* vShader, const char* fShader) const;
    void ApplyOrtho(float maxX, float maxY) const;
    void ApplyRotation(float degrees) const;
    float m_desiredAngle;
    float m_currentAngle;
    GLuint m_simpleProgram;
    GLuint m_framebuffer;
    GLuint m_renderbuffer;
};

As you might expect, the implementation of Render needs to be replaced. Flip back to Example 1-11 to compare it with Example 1-16.

Example 1-16. Render with OpenGL ES 2.0
void RenderingEngine2::Render() const
{
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    
    ApplyRotation(m_currentAngle);
    
    GLuint positionSlot = glGetAttribLocation(m_simpleProgram, "Position");
    GLuint colorSlot = glGetAttribLocation(m_simpleProgram, "SourceColor");
    
    glEnableVertexAttribArray(positionSlot);
    glEnableVertexAttribArray(colorSlot);
    
    GLsizei stride = sizeof(Vertex);
    const GLvoid* pCoords = &Vertices[0].Position[0];
    const GLvoid* pColors = &Vertices[0].Color[0];
    
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);
    
    GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex);
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);
    
    glDisableVertexAttribArray(positionSlot);
    glDisableVertexAttribArray(colorSlot);
}

As you can see, the 1.1 and 2.0 versions of Render are quite different, but at a high level they basically follow the same sequence of actions.

Framebuffer objects have been promoted from a mere OpenGL extension to the core API. Luckily OpenGL has a very strict and consistent naming convention, so this change is fairly mechanical. Simply remove the OES suffix everywhere it appears. For function calls, the suffix is OES; for constants the suffix is _OES. The constructor is very easy to update:

RenderingEngine2::RenderingEngine2()
{
    glGenRenderbuffers(1, &m_renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
}

The only remaining public method that needs to be updated is Initialize, shown in Example 1-17.

Example 1-17. RenderingEngine2 initialization
void RenderingEngine2::Initialize(int width, int height)
{
    // Create the framebuffer object and attach the color buffer.
    glGenFramebuffers(1, &m_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,
                              GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER,
                              m_renderbuffer);
    
    glViewport(0, 0, width, height);
    
    m_simpleProgram = BuildProgram(SimpleVertexShader, SimpleFragmentShader);
    
    glUseProgram(m_simpleProgram);
    
    // Initialize the projection matrix.
    ApplyOrtho(2, 3);
    
    // Initialize rotation animation state.
    OnRotate(DeviceOrientationPortrait);
    m_currentAngle = m_desiredAngle;
}

This calls the private method BuildProgram, which in turn makes two calls on the private method BuildShader. In OpenGL terminology, a program is a module composed of several shaders that get linked together. Example 1-18 shows the implementation of these two methods.

Example 1-18. BuildProgram and BuildShader
GLuint RenderingEngine2::BuildProgram(const char* vertexShaderSource,
                                      const char* fragmentShaderSource) const
{
    GLuint vertexShader = BuildShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fragmentShader = BuildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
    
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    glLinkProgram(programHandle);
    
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
        exit(1);
    }
    
    return programHandle;
}

GLuint RenderingEngine2::BuildShader(const char* source, GLenum shaderType) const
{
    GLuint shaderHandle = glCreateShader(shaderType);
    glShaderSource(shaderHandle, 1, &source, 0);
    glCompileShader(shaderHandle);
    
    GLint compileSuccess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
    
    if (compileSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
        exit(1);
    }
    
    return shaderHandle;
}

You might be surprised to see some console I/O in Example 1-18. This dumps out the shader compiler output if an error occurs. Trust me, you’ll always want to gracefully handle these kind of errors, no matter how simple you think your shaders are. The console output doesn’t show up on the iPhone screen, but it can be seen in Xcode’s Debugger Console window, which is shown via the RunConsole menu. See Figure 1-10 for an example of how a shader compilation error shows up in the console window.

Debugger console
Figure 1-10. Debugger console

Note that the log in Figure 1-10 shows the version of OpenGL ES being used. To include this information, go back to the GLView class, and add the lines in bold:

if (api == kEAGLRenderingAPIOpenGLES1) {
    NSLog(@"Using OpenGL ES 1.1");
    m_renderingEngine = CreateRenderer1();
} else {
    NSLog(@"Using OpenGL ES 2.0");
    m_renderingEngine = CreateRenderer2();
}

The preferred method of outputting diagnostic information in Objective-C is NSLog, which automatically prefixes your string with a timestamp and follows it with a carriage return. (Recall that Objective-C string objects are distinguished from C-style strings using the @ symbol.)

Return to RenderingEngine2.cpp. Two methods remain: ApplyOrtho and ApplyRotation. Since ES 2.0 does not provide glOrthof or glRotatef, you need to implement them manually, as seen in Example 1-19. (In the next chapter, we’ll create a simple math library to simplify code like this.) The calls to glUniformMatrix4fv provide values for the uniform variables that were declared in the shader source.

Example 1-19. ApplyOrtho and ApplyRotation
void RenderingEngine2::ApplyOrtho(float maxX, float maxY) const
{
    float a = 1.0f / maxX;
    float b = 1.0f / maxY;
    float ortho[16] = {
        a, 0,  0, 0,
        0, b,  0, 0,
        0, 0, -1, 0,
        0, 0,  0, 1
    };
    
    GLint projectionUniform = glGetUniformLocation(m_simpleProgram, "Projection");
    glUniformMatrix4fv(projectionUniform, 1, 0, &ortho[0]);
}

void RenderingEngine2::ApplyRotation(float degrees) const
{
    float radians = degrees * 3.14159f / 180.0f;
    float s = std::sin(radians);
    float c = std::cos(radians);
    float zRotation[16] = {
        c, s, 0, 0,
       -s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    };
    
    GLint modelviewUniform = glGetUniformLocation(m_simpleProgram, "Modelview");
    glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]);
}

Again, don’t be intimidated by the matrix math; I’ll explain it all in the next chapter.

Next, go through the file, and change any remaining occurrences of RenderingEngine1 to RenderingEngine2, including the factory method (and be sure to change the name of that method to CreateRenderer2). This completes all the modifications required to run against ES 2.0. Phew! It’s obvious by now that ES 2.0 is “closer to the metal” than ES 1.1.

Wrapping Up

This chapter has been a headfirst dive into the world of OpenGL ES development for the iPhone. We established some patterns and practices that we’ll use throughout this book, and we constructed our first application from the ground up — using both versions of OpenGL ES!

In the next chapter, we’ll go over some graphics fundamentals and explain the various concepts used in HelloArrow. If you’re already a computer graphics ninja, you’ll be able to skim through quickly.



[1] Hart, John C. Ray Tracing in Graphics Hardware. SPEC Presentation at SIGGRAPH, 2003.

[2] Xcode doesn’t care whether you use hpp or h for headers; we use this convention purely for the benefit of human readers.

Get iPhone 3D Programming now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.