In the last post, we covered how to call OpenGL from C on Android by using the NDK; in this post, we’ll call into the same common code from an Objective-C codebase which will run on an iOS device.
Prerequisites
- A Mac with a suitable IDE installed.
- An iOS emulator, or a provisioned iPhone or iPad.
You’ll also need to have completed the first post in this series. If not, then you can also download the code from GitHub so that you can follow along.
We’ll be using Xcode in this lesson. There are other IDEs available, such as AppCode. If you don’t have a Mac available for development, there is more info on alternatives available here:
- How can I develop for iPhone using a Windows development machine?
- Starting iPhone app development in Linux?
For this article, I used Xcode 4.6.3 with the iOS 6.1 Simulator.
Getting started
We’ll create a new project from an Xcode template with support for OpenGL already configured. You can follow along all of the code at the GitHub project.
To create the new project, open Xcode, and select File->New->Project…. When asked to choose a template, select Application under iOS, and then select OpenGL Game and select Next. Enter ‘Air Hockey’ as the Product Name, enter whatever you prefer for the Organization Name and Company Identifier, select Universal next to Devices, and make sure that Use Storyboards and Use Automatic Reference Counting are both checked, then select Next.
Place the project in a new folder called ios inside of the airhockey folder that we worked with from the previous post. This means that we should now have three folders inside of airhockey: android, common, and ios. Don’t check Create local git repository for this project, as we already setup a git repository in the previous post.
Once the project’s been created, you should see a new folder called Air Hockey inside of the ios folder, and inside of Air Hockey, there’ll be another folder called Air Hockey, as well as a project folder called Air Hockey.xcodeproj.
Flattening the Xcode project
I personally prefer to flatten this all out and put everything in the ios folder, and the following steps will show you how to do this; please feel free to skip this section if you don’t mind having the nested folders:
- Close the project in Xcode, and then move all of the files inside of the second Air Hockey folder so that they are directly inside of the ios folder.
- Move Air Hockey.xcodeproj so that it’s also inside of the ios folder. The extra Air Hockey folders can now be deleted.
- Right-click Air Hockey.xcodeproj in the Finder and select Show Package Contents.
- Edit project.pbxproj in a text editor, and delete all occurrences of ‘Air Hockey/’.
- Go back to the ios folder and open up Air Hockey.xcodeproj in Xcode.
- Select View->Navigator->Show Project Navigator and View->Utilities->Show File Inspector.
- Select Air Hockey in the Project Navigator on the left. On the right in the File Inspector, click the button under Relative to Group, to the right of Air Hockey, select some random folder (this is to work around a bug) and select Choose, then click it again and select the ios folder this time.
The project should now be able to build OK, with all files in the ios folder. More information can be found here: How do you move an Xcode 4.2 project file to another folder?
Adding a reference to the common code
Here’s how we add our common code to the project:
- Right-click the project root in the Project Navigator (the item that looks like “Air Hockey, 1 target, iOS SDK 6.1”).
- Select Add Files to “Air Hockey”….
- Select the common folder, which will be located next to the ios folder, make sure that Copy items into destination group’s folder (if needed) is not checked, that Create groups for any added folders is selected, and that Air Hockey is selected next to Add to targets, then select Add.
You should now see the common folder appear in the Project Navigator, with game.h and game.c inside.
Understanding how iOS manages OpenGL through the GLKit
framework
When we created a new project with the OpenGL Game template, Xcode set things up so that when the application is launched, it displays an OpenGL view on the screen, and drives that view with a special view controller. A view controller in iOS manages a set of views, and can be thought of as being sort of like the iOS counterpart of an Android Activity.
When the application is launched, the OS reads the storyboard file, which tells it to create a new view controller that is subclassed from GLKViewController
and add it to the application’s window. This view controller is part of the GLKit
framework and provides an OpenGL ES rendering loop. It can be configured with a preferred frame rate, and it can also automatically handle application-level events, such as pausing the rendering loop when the application is about to go to the background.
That GLKViewController
contains a GLKView
as its root view, which is what creates and manages the actual OpenGL framebuffer. This GLKView
is automatically linked to the GLKViewController
, so that when it’s time to draw a new frame, it will call a method called drawInRect:
in our GLKViewController
.
Before moving on to the next step, you may want to check out the default project by running it in the simulator, just to see what it looks like.
Calling our common code from the view controller
The default code in the view controller does a lot more than we need, since it creates an entire demo scene. We want to keep things simple for now and just see that we can call OpenGL from C and wrap that with the view controller, so let’s open up ViewController.m, delete everything, and start off by adding the following code:
#import "ViewController.h" #include "game.h" @interface ViewController () { } @property (strong, nonatomic) EAGLContext *context; - (void)setupGL; @end
This includes game.h so that we can call our common functions, defines a new property to hold the EAGL context, and declares a method called setupGL:
which we’ll define soon. Let’s continue the code:
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; if (!self.context) { NSLog(@"Failed to create ES context"); } GLKView *view = (GLKView *)self.view; view.context = self.context; view.drawableDepthFormat = GLKViewDrawableDepthFormat24; [self setupGL]; } - (void)dealloc { if ([EAGLContext currentContext] == self.context) { [EAGLContext setCurrentContext:nil]; } }
Once the GLKView
has been loaded into memory, viewDidLoad:
will be called so that we can initialize our OpenGL context. We initialize an OpenGL ES 2 context here and assign it to the context
property by calling:
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]
This allocates a new instance of an EAGLContext
, which manages all of the state and resources required to render using OpenGL ES. We then initialize that instance by calling initWithAPI:
, passing in a special token which tells it to initialize the context for OpenGL ES 2 rendering.
For those of you not used to Objective-C syntax, here’s what this could look like if it were using Java syntax:
this.context = new EAGLContext().initWithAPI(kEAGLRenderingAPIOpenGLES2);
Once we have an EAGLContext
, we assign it to the view, we configure the view’s depth buffer format, and then we call the following to do further OpenGL setup:
[self setupGL]
We’ll define this method further below. dealloc:
will be called when the view controller is destroyed, so there we release the EAGLContext
if needed by calling the following:
[EAGLContext setCurrentContext:nil]
Let’s complete the code for ViewController.m:
- (void)setupGL { [EAGLContext setCurrentContext:self.context]; on_surface_created(); on_surface_changed(); } - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { on_draw_frame(); } @end
Here is where we call our game code to do the actual rendering. In setupGL:
, we set the EAGLContext
so that we have a valid context to use for drawing, and then we call on_surface_created()
and on_surface_changed()
from our common code. Every time a new frame needs to be drawn, drawInRect:
will be called, so there we call on_draw_frame()
.
Why don’t we also need to set the context from drawInRect:
? This method is actually a delegate method which is declared in GLKViewDelegate
and called by the GLKView
, and the view takes care of setting the context and framebuffer target for us before it calls our delegate. For those of you from the Java world, this is like having our class implement an interface and passing ourselves as a listener to another class, so that it can call us back via that interface. Our view controller automatically set itself as the delegate when it was linked to the GLKView
by the storyboard.
We don’t have to do things this way — we could also subclass GLKView
instead and override its drawRect:
method. Delegation is simply a preferred pattern in iOS when subclassing isn’t required.
As a quick reminder, here’s what we had defined in our three functions from game.c:
void on_surface_created() { glClearColor(1.0f, 0.0f, 0.0f, 0.0f); } void on_surface_changed() { // No-op } void on_draw_frame() { glClear(GL_COLOR_BUFFER_BIT); }
So, when we actually run our project, we should expect the screen to get cleared to red.
Before we can build and run the project, we’ll need to add a glwrapper.h header to the project, like we did for the Android project in the previous post. In the same folder as ViewController.m, add a new header file called glwrapper.h, and add the following contents:
#include <OpenGLES/ES2/gl.h>
Building and running the project
We should now be able to build and run the project in the iOS simulator. Click the play button to run the app and launch the simulator. Once it’s launched, you should see a screen similar to the following image:
And that’s it! By using GLKit
, we can easily wrap our OpenGL code and call it from Objective-C.
To tell iOS and the App Store that our application should not be displayed to unsupported devices, we can add the key ‘opengles-2’ to Air Hockey-Info.plist, inside Required device capabilities.
Exploring further
The full source code for this lesson can be found at the GitHub project. For further reading, I recommend the following excellent intro to GLKit
, which goes into more detail on using GLKView
, GLKViewController
and other areas of GLKit
:
Beginning OpenGL ES 2.0 with GLKit Part 1
In the next part of this series, we’ll take a look at using emscripten to create a new project that also calls into our common code and compiles it for the web. I am coming to iOS from a background in Java and Android and I am new to iOS and Objective-C, so please let me know if anything doesn’t make sense here, and I’ll go and fix it up. 🙂
About the book
Android is booming like never before, with millions of devices shipping every day. In OpenGL ES 2 for Android: A Quick-Start Guide, you’ll learn all about shaders and the OpenGL pipeline, and discover the power of OpenGL ES 2.0, which is much more feature-rich than its predecessor.
It’s never been a better time to learn how to create your own 3D games and live wallpapers. If you can program in Java and you have a creative vision that you’d like to share with the world, then this is the book for you.
5 thoughts on “Calling OpenGL from C on iOS, Sharing Common Code with Android”