May 06

Before I post anymore code, I figured now would be a good time to backup and show you how to create an OpenGL ES project for the iPhone.

Creating an OpenGL ES project is very simple. Xcode has a wizard for that which will be the starting point. What I’m going to do is show you how to tweak it a bit so that you can do most of your programming in C++.

Step 1: Create an OpenGL ES iPhone Project

  1. Launch Xcode
  2. Under iPhone OS click: Application
  3. Click: OpenGL ES Application
  4. Click: Choose …
  5. For Save As change to: MyWorld1
  6. Click: Save

Step 2: Prepare for C++

  1. Under Groups & Files expand the Classes folder
  2. Rename EAGLView.m to EAGLView.mm – see my post on Mixing C++ and Objective-C if you don’t understand why.
  3. Rename MyWorld1AppDelegate.m to MyWorld1AppDelegate.mm

Step 3: Add a C++ class for your world

  1. Right click on the Classes folder and select Add / New File …
  2. Under Mac OS X click C and C++
  3. Click on the C++ File icon
  4. Click: Next
  5. Highlight the whole filename and change it to MyWorld1.mm
  6. Make sure Also create “MyWorld1.h” is checked
  7. Click: Finish

Step 4: Modify MyWorld1.h

Change the contents of MyWorld1.h to the following:

/*
 *  MyWorld1.h
 *  MyWorld1
 *
 *  Created by Mitchell Allen on 5/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#ifndef  __MYWORLD1__
#define  __MYWORLD1__
 
//  OpenGL ES includes for the iPhone only
#ifdef TARGET_OS_IPHONE
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
 
#define SCREEN_X	480
#define SCREEN_Y	320 
 
#endif
 
class MyWorld1 {
 
public:
 
	void setupGL();
	void tearDownGL();
	void resizeGL( int iWidth, int iHeight );
	void paintGL();
 
	void animate();
 
	void clickStart( int x, int y );
	void clickStop( int x, int y );
	void clickDrag( int x, int y );
	void clickCancel( int x, int y );
 
};
 
#endif

Step 5: Modify MyWorld1.mm

Change the contents of MyWorld1.mm to the following:

/*
 *  MyWorld1.mm
 *  MyWorld1
 *
 *  Created by Mitchell Allen on 5/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "MyWorld1.h"
 
void MyWorld1::setupGL()
{
	NSLog( @"MyWorld1View::setupGL()" );
 
	// Put your OpenGL ES setup code here.
}
 
void MyWorld1::tearDownGL()
{
	NSLog( @"MyWorld1View::tearDownGL()" );
 
	// Put your OpenGL ES teardown code here.
}
 
void MyWorld1::resizeGL( int w, int h )
{
	// Put your OpenGL ES resize code here.
}
 
void MyWorld1::paintGL()
{
	// Put your OpenGL ES paint code here.
}
 
void MyWorld1::animate()
{
	// Put your OpenGL ES animation code here.
}
 
void MyWorld1::clickStart( int x, int y )
{
	NSLog( @"MyWorld1View::clickStart( %i, %i )", x, y );
 
	// Handle start of user touching the screen
}
 
void MyWorld1::clickStop( int x, int y )
{
	// On iPhone - will not get called if clickCancel is fired.
 
	NSLog( @"MyWorld1View::clickStop( %i, %i )", x, y );
 
	// Handle end of user touching the screen
}
 
void MyWorld1::clickDrag( int x, int y )
{
	NSLog( @"MyWorld1View::clickDrag( %i, %i )", x, y );
 
	// Handle user dragging finger across the screen
}
 
void MyWorld1::clickCancel( int x, int y )
{
	// On iPhone, will be called instead of clickStop - like when phone rings.
 
	NSLog( @"MyWorld1View::clickCancel( %i, %i )", x, y );
 
	// Handle app being stopped by something like the phone ringing on an iPhone.
}

Step 6: Modify EAGLView.h

Change the contents of EAGLView.h to the following. I’ve put my initials: MCA – near code I added or changed:

//
//  EAGLView.h
//  MyWorld1
//
//  Created by Mitchell Allen on 5/6/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
 
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
 
// MCA - Update
#include "MyWorld1.h"
 
/*
This class wraps the CAEAGLLayer from CoreAnimation into a convenient UIView subclass.
The view content is basically an EAGL surface you render your OpenGL scene into.
Note that setting the view non-opaque will only work if the EAGL surface has an alpha channel.
*/
@interface EAGLView : UIView {
 
@private
    /* The pixel dimensions of the backbuffer */
    GLint backingWidth;
    GLint backingHeight;
 
    EAGLContext *context;
 
    /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
    GLuint viewRenderbuffer, viewFramebuffer;
 
    /* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */
    GLuint depthRenderbuffer;
 
    NSTimer *animationTimer;
    NSTimeInterval animationInterval;
 
	// MCA
	MyWorld1	m_world;	// C++ object
}
 
@property NSTimeInterval animationInterval;
 
- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView;
 
// MCA
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
 
@end

Step 7: Modify EAGLView.mm

Change the contents of EAGLView.mm to the following. I’ve put my initials: MCA – near code I added or changed:

//
//  EAGLView.m
//  MyWorld1
//
//  Created by Mitchell Allen on 5/6/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
 
 
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
 
#import "EAGLView.h"
 
////////////////////////////////////////////////////
// MCA - IMPORTANT!  Change from 0 to 1
#define USE_DEPTH_BUFFER 1
 
// A class extension to declare private methods
@interface EAGLView ()
 
@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;
 
- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;
 
@end
 
 
@implementation EAGLView
 
@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;
 
 
// You must implement this method
+ (Class)layerClass {
    return [CAEAGLLayer class];
}
 
 
//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])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
 
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
 
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
 
        if (!context || ![EAGLContext setCurrentContext:context]) {
            [self release];
            return nil;
        }
 
		//////////////////////
		// MCA
		m_world.setupGL();
		m_world.resizeGL( backingWidth, backingHeight );
		////////////////////////
 
        animationInterval = 1.0 / 60.0;
    }
    return self;
}
 
 
- (void)drawView {
 
    [EAGLContext setCurrentContext:context];
 
	/////////////////////////
	// MCA
	m_world.animate();
 
	m_world.resizeGL( backingWidth, backingHeight );
 
	m_world.paintGL();
	///////////////////////////
 
 
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
 
 
- (void)layoutSubviews {
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
    [self drawView];
}
 
 
- (BOOL)createFramebuffer {
 
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
 
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
 
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
 
    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }
 
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
 
    return YES;
}
 
 
- (void)destroyFramebuffer {
 
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
 
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}
 
 
- (void)startAnimation {
    self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
 
 
- (void)stopAnimation {
    self.animationTimer = nil;
}
 
 
- (void)setAnimationTimer:(NSTimer *)newTimer {
    [animationTimer invalidate];
    animationTimer = newTimer;
}
 
 
- (void)setAnimationInterval:(NSTimeInterval)interval {
 
    animationInterval = interval;
    if (animationTimer) {
        [self stopAnimation];
        [self startAnimation];
    }
}
 
 
- (void)dealloc {
 
    [self stopAnimation];
 
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
 
	/////////////////////////
	// MCA
	m_world.tearDownGL();
 
    [context release];  
    [super dealloc];
}
 
// MCA
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event {
 
	// NSLog(@"DEBUG: Touches began" );
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickStart( pos.x, pos.y );
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches cancelled");
 
	// Will be called if something happens - like the phone rings
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickCancel( pos.x, pos.y );
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches ended");
 
	// Note that if phone rings, touchesCancelled will fire instead of touchesEnded
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickStop( pos.x, pos.y );
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches moved" );
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickDrag( pos.x, pos.y );
}
 
 
@end

Step 8: Click Build and Go

When you run the project you won’t see much – because this is an empty shell waiting for you to add your OpenGL code. If you click on the screen in the simulator, the clicks will appear in the Debugger Console.

Tags: ,

Dec 04

Here are my notes on getting started with the iPhone SDK (Software Development Kit). I encountered a few issues which I will describe here.

You Need a Mac

To develop for the iPhone and iPod Touch you need a Mac running Leopard (10.5). Earlier versions of the Mac OS won’t do. Bundled with Leopard is a free IDE called Xcode. Once you have Xcode up and running, you need to go to http://developer.apple.com/iphone/, create a login, and download the iPhone SDK on to your Mac.

Objective-C

Some of you may freak out at this point at the thought of learning another programming language. The de facto standard for programming for the iPhone is Objective-C. You can also do some coding in C++. But most everyone is working in Objective-C. The Apple site has tutorials on iPhone development and there are tutorials out there on Objective-C. So I’m not going to repeat that stuff here.

Simulation is Free, Distribution is $99

You can build your application on the Mac using Xcode and run it in an iPhone simulator for free. But if you actually want to try your code on your iPhone or iPod Touch, you are going to have to plunk down $99 and join the iPhone Developer Program (or more if you are part of a larger enterprise).

Once signed up you will find that when you login to http://developer.apple.com/iphone/ you have a new button that leads to the iPhone Developer Program Portal. This is where you go to manage certificates that you will need to develop for the device. The steps are laid out for the most part. So again I’m not going to repeat instructions that are available online. But I did run into a few issues that I will document here.

Issue #1: Unable to locate a suitable developer disk image

I own the latest iPod Touch that comes with 2.1.1 installed. When I tried to create my app in Xcode I got an error dialog that said “Unable to locate a suitable developer disk image.” I found a solution to my problem here. To quote from the thread:

You will need to create the following symlink:
…/iPhoneOS.platform/DeviceSupport/2.1.1/
pointing at:
…/iPhoneOS.platform/DeviceSupport/2.1/

To do that, open up a terminal window (command prompt) and enter the following:

cd /Developer/Platforms/iPhoneOS.platform/DeviceSupport
ln -s 2.1/ 2.1.1

Issue #2: Signing Your Name

Once you have access to the developer portal you will find instructions under the Provision / How To section that tell you to do this:

In the Project Info window select the ‘Build’ tab and enter “iPhone Developer: YourFirstName YourLastName” in the ‘Code Signing Identity’->’Any iPhone OS’ field setting. This is also the Common Name of your iPhone Development Certificate. (Note: Be sure to include the space between the ‘:’ and ‘YourFirstName’.)

So reading that, I entered “iPhone Developer: Mitch Allen” – it didn’t work. Why? Because when I signed up for the developer program I used my full name so it would match my credit card. What I needed to do was set this to “iPhone Developer: Mitchell Allen.”

Issue #3: Error 0xE800003A

When trying to load and run the application on the actual device you get this rather cryptic error: “Your mobile device has encountered an unexpected error (0xE800003A) during the install phase.”

The closest thing I found to a solution was here.

The instructions for creating certificates for your application tell you to create an identifier like this: com.yourcompany.yourapp. I was experimenting by creating an app based on Conways Game of Life. So my identifier was “com.mitchallen.life2.” To make the dreaded error code go away I almost followed the instructions in the thread. What I did was edit the Bundle Identifier in info.plist to be:

com.mitchallen.${PRODUCT_NAME:Identifier}

I hope this helps!

You may also want to read this article: The iPhone Development Story.

Tags: ,