Psellos
Contemporary Development With Functional Programming

IcosaBlue: OpenGL ES App for iOS

August 20, 2011

Posted by Jeffrey

Rotating blue icosahedron

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 return CAEAGLLayer, 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.