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: ,

2 Responses to “Creating an OpenGL ES project for the iPhone”

  1. David Says:

    This has made my life about 10 times easier. Cheers!!!!

  2. Anton Says:

    I had some uncertainties about coordinates of touches… resolved by trying this sample in about 2 minutes. Thanks a million for publishing this!

Leave a Reply