IcosaBlue: OpenGL ES App for iOS
Posted by Jeffrey
I’ve put together an OCaml OpenGL ES iPhone app, as a demonstration of how to use LablGLES. LablGLES is my name for the OCaml OpenGL ES 1.1 interface described here. It’s based on LablGL, the OCaml OpenGL interface by Jacques Garrigue and others.
The app is named IcosaBlue, because it draws a rotating blue icosahedron. I think it looks pretty cool, but a rotating three-dimensional shape is mostly just the “Hello World” of OpenGL.
To satisfy all tastes, I’ve packaged this simple app up in three ways.
• A prebuilt binary for the iPhone Simulator. You just download it and run it.
• A source release for the iPhone Simulator.
• A source release for the iPhone (or other iOS device).
All of these are the same app (same sources and build system). Just the packaging is different. They’re pretty easy to figure out; you could just download them and start playing around (such as changing to a counter-rotating orange icosahedron). But in the following sections I’ll describe in a little more detail how to get them running.
For any of them you need an installation of Xcode with the iOS SDK. For the first two, that’s all you need. For the third format, you need to be a registered Apple developer.
The app has been built and tested with Xcode 4.0.2. It will work with any recent Xcode release.
Please note that a few of the command lines of the following discussions are too long to fit on a single line of a typical browser window. In a lot of cases there is no good place to split them into smaller lines, usually because of a long filename or URL. Take care that you enter them as a single line if you’re typing them in by hand.
Running Prebuilt IcosaBlue Binary in iPhone Simulator
If you want to run the prebuilt IcosaBlue binary in the iPhone simulator (to prove to yourself that it works), just download the simulator binary for IcosaBlue 1.0.3 from Psellos:
$ curl -O -s http://psellos.com/pub/icosablue/icosablue-simapp-1.0.3.tgz
$ ls -l icosablue-simapp-1.0.3.tgz
-rw-r--r-- 1 psellos staff 213160 Aug 20 08:48 icosablue-simapp-1.0.3.tgz
To save typing, you can download it directly from this link. (It will
appear in your browser’s Downloads
directory.)
Now untar.
$ tar -xzf icosablue-simapp-1.0.3.tgz
This creates a directory named icosablue-simapp-1.0.3
in which you
should see the binary IcosaBlue
and a script named runsim
that runs
the app in the iPhone Simulator. To run IcosaBlue, just cd
and run
the script:
$ cd icosablue-simapp-1.0.3
$ runsim
If you don’t have Xcode installed in the usual place, you should edit the script appropriately before running it.
Note: for simplicity, this script uses an undocumented (but cool) interface of the iPhone Simulator to run it from the command line rather than from inside Xcode.
Building IcosaBlue from Source for iPhone Simulator
To build from source, you need Xcode (I used Xcode 4.0.2). You also need an OCaml compiler that conforms to the iPhone Simulator ABI. You can use the one I put together—binaries and instructions for building from source are given here.
Download the iPhone Simulator sources for IcosaBlue 1.0.3 from Psellos:
$ curl -O -s http://psellos.com/pub/icosablue/icosablue-sim-1.0.3.tgz
$ ls -l icosablue-sim-1.0.3.tgz
-rw-r--r-- 1 psellos staff 68350 Aug 20 09:46 icosablue-sim-1.0.3.tgz
To save typing, you can download it directly from this link. (It will
appear in your browser’s Downloads
directory.)
Now, untar and cd
into the new directory.
$ tar -xzf icosablue-sim-1.0.3.tgz
$ cd icosablue-sim-1.0.3
$ ls
IcosaBlue
IcosaBlueSim.xcodeproj
LablGLES
What you see are a directory containing the sources for the test app, a small Xcode project, and a directory containing the sources for LablGLES. The Xcode project isn’t for building; it’s for examining and modifying the nibfile (as explained below).
Building and running IcosaBlue is done by IcosaBlue/Makefile.iossim
and LablGLES/Makefile.config
. You may need to change some of the
specific settings in these files:
First, IcosaBlue/Makefile.iossim
has lines like these:
PLAT = /Developer/Platforms/iPhoneSimulator.platform
SDK = /Developer/SDKs/iPhoneSimulator4.3.sdk
OCAMLDIR = /usr/local/ocamlxsim
Change PLAT
to the location of your iPhoneSimulator platform
directory, part of the Xcode iPhone SDK. Change SDK
to the iPhone SDK
you wish to use. Probably you want to set it to the most recent SDK
that you have installed. Change OCAMLDIR
to the location of your
OCaml compiler for the iPhone Simulator.
Next, LablGLES/Makefile.config
has lines like these:
OCAMLBINDIR=/usr/local/ocamlxsim/bin
SDK=/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.3.sdk
This is the same information in a slightly different form. Make the
same changes as for Makefile.iossim
above.
Now you can build and run the app:
$ cd IcosaBlue
$ make -f Makefile.iossim
cd ../LablGLES; make
...
cd src && make opt
/usr/local/ocamlxsim/bin/ocamlopt -c -ccopt -arch -ccopt i386 -ccopt -isysroot -ccopt /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.3.sdk -ccopt -D__IPHONE_OS_VERSION_MIN_REQUIRED=30200 -I +labltk raw.ml
...
/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.2 -arch i386 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.3.sdk -gdwarf-2 -D__IPHONE_OS_VERSION_MIN_REQUIRED=30200 -isystem /usr/local/ocamlxsim/lib/ocaml -DCAML_NAME_SPACE -fobjc-legacy-dispatch -fobjc-abi-version=2 -c ViewDelegator.m
...
/usr/local/ocamlxsim/bin/ocamlc -I ../LablGLES/release -g -c wrapper.mli
/usr/local/ocamlxsim/bin/ocamlopt -pp /usr/local/ocamlxsim/bin/camlp4o -I ../LablGLES/release -g -cc '/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.2' -ccopt '-arch i386 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.3.sdk -gdwarf-2 -D__IPHONE_OS_VERSION_MIN_REQUIRED=30200 -isystem /usr/local/ocamlxsim/lib/ocaml -DCAML_NAME_SPACE' -c wrapper.ml
...
ibtool --compile IcosaBlue.nib IcosaBlue.xib
/bin/echo -n 'APPL????' > PkgInfo
$ make -f Makefile.iossim execute
$
You should see the IcosaBlue app running in the iPhone Simulator.
Note: for simplicity,
make execute
uses the same undocumented (but cool) interface for
running the iPhone Simulator from the command line.
Building IcosaBlue from Source for iPhone
To build and run apps on iPhone you need Xcode, and you need to be a registered Apple iOS developer. You also need an OCaml cross-compiler that conforms to the iPhone ABI. You can use the one I use, a modified version of OCaml 3.10.2, with patches by others and by us (Psellos). Information and instructions for building it are here.
Download the iPhone sources for IcosaBlue 1.0.3 from Psellos:
$ curl -O -s http://psellos.com/pub/icosablue/icosablue-ios-1.0.3.tgz
$ ls -l icosablue-ios-1.0.3.tgz
-rw-r--r-- 1 psellos staff 70139 Aug 20 10:45 icosablue-ios-1.0.3.tgz
To save typing, you can download it directly from this link. (It will
appear in your browser’s Downloads
directory.)
Now, untar and cd into the new directory.
$ tar -xzf icosablue-ios-1.0.3.tgz
$ cd icosablue-ios-1.0.3
$ ls
IcosaBlue
IcosaBlue.xcodeproj
LablGLES
What you see are a directory containing the sources for the test app, the Xcode project for bulding it, and a directory containing the sources for LablGLES.
Most of the work of building IcosaBlue for iPhone is done by
IcosaBlue/Makefile.ios
and LablGLES/Makefile.config
. You may need
to change some of the specific settings in these files.
First, IcosaBlue/Makefile.ios
has lines like these:
PLAT = /Developer/Platforms/iPhoneOS.platform
SDK = /Developer/SDKs/iPhoneOS4.3.sdk
OCAMLDIR = /usr/local/ocamlxarm
Change PLAT
to the location of your iPhoneOS platform directory, part
of the Xcode iPhone SDK. Change SDK
to the iPhone SDK you wish to
use. Probably you want to set it to the most recent SDK that you have
installed. Change OCAMLDIR
to the location of your OCaml cross
compiler.
Next, LablGLES/Makefile.config
has lines like these:
OCAMLBINDIR=/usr/local/ocamlxarm/bin
SDK=/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk
This is the same information in a slightly different form. Make the
same changes as for Makefile.ios
above.
Now you want to use Xcode to build the app. Xcode handles all the packaging and code signing, which is a lot more complicated when dealing with an actual iOS device. In the following discussion, I assume you’re using Xcode 4, and that IcosaBlue is your only open project. The basic idea is the same for Xcode 3, but the details are all slightly different.
To open Xcode, you can use the following command:
$ open IcosaBlue.xcodeproj
This starts Xcode and opens IcosaBlue as an Xcode project. You can also open the project file (actually a directory) from Xcode’s File -> Open menu.
Make sure Xcode is building for iPhone (not the simulator) and is building the IcosaBlue target (not LablGLES). At the upper left is a drop-down selector labeled Scheme. Open the selector and select IcosaBlue | iOS Device.
Next make sure code signing is configured properly. This is hard to describe, but not so hard to do.
Make sure you are in the Project Navigator pane at the upper left. The little icon of a folder should be highlighted. This is the default pane, so you probably don’t have to worry about it.
Click on the IcosaBlue project at the top of the Navigator pane. It has a blue generic application icon (coincidentally). Now in the middle pane you should see the project and a list of targets.
Click on the IcosaBlue target (not the project, but the target). It has a generic application icon that’s not blue. Now we’re getting warmer. To the right you’ll see many panes of info about the IcosaBlue target.
Click on the Summary pane. The Identifier field shows the so-called app id of the target application. Set the app id to an appropriate value for your development environment. In the downloaded example, the initial value is
com.psellos.IcosaBlue
. If you’re using Automatic Device Provisioning, you probably don’t need to change it. The wild-card profile will allow you to compile an app with any id.Now click on the Build Settings pane and look at the Code Signing section. Make sure the Code Signing Identity is set to something reasonable for your development environment. For me it says: currently matches ‘iPhone Developer: Jeffrey Scofield (XXX)’ in ‘Team Provisioning Profile: *’. The Team Provisioning Profile is the one created for Automatic Device Provisioning; it’s a wild-card profile that matches every app id. If yours looks similar, it’s probably OK to leave it as it is.
You can now build the app. If you have an iOS device attached, you can build and run it.
To build, click on the Product -> Build menu item.
To run, click on the Product -> Run menu item.
If things go right, you’ll see an app running on your iPhone that looks like the screenshot above. A rotating blue icosahedron.
Theory of Operation
IcosaBlue is very similar to the simplest OCaml iPhone app,
Portland. The only real difference is that IcosaBlue uses OpenGL
ES for drawing on the screen, while Portland uses Cocoa Touch. Like
Portland, IcosaBlue has just one class that really does anything, named
Icosactlr
. There is a singleton instance of this class that
participates in the UIApplicationDelegate
protocol to receive
notifications of state changes. Let’s call this instance Ici for short. In particular,
Ici notices when the app
is activated and deactivated.
The startup of an iOS app is controlled by a nib file, generated by
Interface Builder. For IcosaBlue, the file is IcosaBlue.xib
. It says
to create Ici, and to
make it the delegate for the containing IcosaBlue app.
At startup, Ici uses Core
Graphics to draw textures for the faces of the icosahedron. (These
textures are what make the icosahedron blue.) It then establishes a
timer that fires periodically, calling its own drawView'
method. This
method invokes OpenGL ES to calculate an updated view of the rotating
icosahedron and display it on the screen.
The geometry of the icosahedron is defined by a module named
Icosamodel
. It allocates the arrays of vertices, normals, and texture
coordinates that are passed to LablGLES for drawing.
Interface to OpenGL ES
At the outer layer, OpenGL ES is handled through a new Objective C class
named ViewDelegatorGL
. It’s a subclass of the Cocoa Touch UIView
that does two special things:
It redefines its
layerClass
method to returnCAEAGLLayer
, the Cocoa Touch class for OpenGL layers. Its layer is instantiated as an instance of this class and thus supports OpenGL ES drawing.It delegates its
layoutSubviews'
method, which gets invoked at startup and whenever the geometry of the view is changed. In IcosaBlue the geometry never changes, so only the startup invocation is performed.
The Icosactlr
instance
Ici is set as the
delegate of the ViewDelegatorGL
. When its layoutSubviews'
method is
invoked, it initializes the OpenGL ES context and draws the first view
of the icosahedron. Each time the timer fires, another view of the
icosahedron is drawn.
All OpenGL ES drawing is done through a framebuffer, as defined by the OpenGL ES framebuffer extension. One of the differences of LablGLES from LablGL is that I added an implementation of this extension.
At the inner layer, OpenGL ES is handled by an instance of the Cocoa
Touch class EAGLContext
. It represents the context required to do
OpenGL ES drawing in iOS. In particular there are methods to:
Associate an EAGLContext with the CAEAGLLayer of a view, so that drawing operations set the contents of the view.
Set the current context, so that subsequent OpenGL ES operations are applied to that particular EAGLContext.
Cause an EAGLContext to be drawn in its associated view.
For IcosaBlue, these contexts are instances of the wrapper type
EAGLContext.t
.
LablGLES inherits from LablGL a module named Raw
that implements
low-level arrays that can live outside the OCaml heap. Most of the
graphical data in LablGLES is passed as Raw arrays. For IcosaBlue, I
adapted the wrapper for the Core Graphics bitmap context type
(CgContext.t
) so that its underlying data is stored in a Raw array.
This allows images generated by Core Graphics to be passed directly to
LablGLES to be used as a texture.
Interface Builder
In versions of Xcode prior to 4.0, Interface Builder was a separate
utility. In Xcode 4.0, it has been integrated as one of the many
specialized editors for project components. If you’re using an older
version of Xcode, you should be able to open the source file
IcosaBlue.xib
directly with Interface Builder. If you’re using Xcode
4 with the iPhone, you can access IcosaBlue.xib
as part of the
IcosaBlue project.
For those using Xcode 4 with the iPhone Simulator, I created a small
project named IcosaBlueSim.xcodeproj
whose only purpose is to examine
and modify the IcosaBlue.xib
file. If you double-click on
IcosaBlueSim.xcodeproj
in the Finder, it will start Xcode on the
project. You can then navigate to the IcosaBlue.xib
file and examine
its contents. (Hint: to see the detailed properties of objects, open
the Utility window at the right of the screen.)
In any case, you’ll see that IcosaBlue.xib
is pretty simple. There
are only three interesting objects: the main window, a ViewDelegatorGL
instance that occupies the whole window, and an instance of
Icosactlr.t
.
There is a connection to the controller from the delegate
outlet of
the application (represented by File’s Owner). This supports the
application startup notification.
There is a connection from the delegate
outlet of the
ViewDelegatorGL
to the controller, and a connection from the
delegator
outlet of the controller back to the ViewDelegatorGL
.
These support the invocation of the layoutSubviews'
method.
Since Interface Builder (IB) doesn’t know anything about OCaml (at least
not yet), it gets its information from the header files for your wrapper
classes. In earlier Xcode versions, you may need to manually tell IB to
load the wrapper header files. To load header files into earlier
versions of IB, click on the File -> Read Class Files menu item, and
navigate to your ObjC header files, i.e., to the wrapper files for your
OCaml classes. Note that you need wrappers only for OCaml classes that
are accessed from ObjC, which should be just a few of them. In
IcosaBlue, the headers are ViewDelegator.h
and wrap.h
.
Discussion
Although I’ve packaged up IcosaBlue as an iPhone app, it would also run on the iPad. The code is written to be independent of the screen geometry, so only the nib file should need to change.
I’ve put together many other apps that run on the iPhone or in the iPhone Simulator. For a list, see our OCaml Programming page.
I’d be very happy to explain any part of IcosaBlue in more detail. The only thing stopping me is my natural modesty. If you have questions, comments, or corrections please email me at jeffsco@psellos.com.