Cocoa and Objective C
Probably the biggest perceived hurdles for entry into Mac OS X programming is Objective C ("C with objects") and Cocoa (Apple's object-oriented application framework). On the iPhone, Cocoa is extended to "Cocoa Touch", which is designed around the touch interface for user interaction. I use Objective C when necessary, but I like to share a lot of code between the Mac and Windows versions of my software. So an early trick I learned was how to bridge Objective C to C++ and vice versa.
If you're new to the Mac development environment, the most important thing I can tell you is that you do not have to use Objective C for everything. Not only can you use C and C++ for desktop and iPhone development, but you can code C and even C++ classes right in an Objective C source module (for C++, you must rename the files from *.m to *.mm). Even better, it is easy to link Objective C-based Cocoa code with C or C++ code modules. It is also a common misunderstanding that all OS X APIs are exposed via Cocoa. Many APIs, such as OpenGL, are indeed C APIs that can be called from just about any programming language that supports the C calling convention.
Building a Bridge
XCode 3.1 comes with a new template for Cocoa Touch OpenGL applications for the iPhone. When creating a new project, just select this template, name your project, and set the folder where you want the source files. Figure 1 shows the default skeleton OpenGL ES application running on the desktop with the iPhone emulator. It is basically nothing more than a spinning colored square (actually, a short triangle strip). Of course, the application is all Objective C and Cocoa. In time, third-party frameworks or open-source projects akin to GLUT or SDL will spring up to enable OpenGL and/or game development on the iPhone without having to master the details of OS X, Objective C, or Cocoa Touch. (I'm hoping Trolltech will bring Qt to the iPhone.)
In the meantime, I have started to create my own C++ bridging framework for iPhone OpenGL game development. My associates and I at Full Sail University (www.fullsail.edu) are planning on creating an iPhone game programming lab and including it in our curriculum. With a little Cocoa knowledge, it is trivial to wire in a C++ bridge class so that programmers who are either more comfortable with C++, or have other technical considerations for doing so can get going with the iPhone SDK.
Listing One is a basic portable OpenGL C++ rendering framework. (The source code for the complete framework is available online at www.ddj.com/code/.) This is just from the header of a simple C++ class that defines the four basic things all OpenGL programs need to do:
- InitializeGL is where any startup initialization code goesload textures, create buffer objects, set initial rendering state, and so on.
- ShutdownGL is just the opposite. This is where textures and other objects can be deleted. InitializeGL and ShutdownGL are always called after the OpenGL rendering context is created, and before it is destroyed, respectively.
- ResizeGL is called at least once, and in windowed systems when the window changes size so that the viewport and projections can be reset as needed.
- PaintGL is called to perform the OpenGL rendering.
This simple framework keeps portable OpenGL and C++ code far away from system internals, Objective C, and Cocoa. I've used this to move code easily from not only OS to OS, but between different application frameworksCarbon, Cocoa, and Qt on the Mac, and low-level C/C++ SDK code, MFC, Qt, WxWidgets, GLUT, SDL, on Windows and others. The trick is to know where in the calling framework to create the class, and call these four member functions.
class MyGLView { public: MyGLView(void); void ResizeGL(int nWidth, int nHeight); void InitializeGL(void); void ShutdownGL(void); void PaintGL(void); };
For this article, I ported from my book The OpenGL SuperBible an example program that loads and displays a model of a ThuderBird (thanks to Ed Womack for providing the model). This example, running on an iPod Touch in Figure 2, uses Vertex Buffer objects and indexed triangles. In the book, I used a cube map to make the cockpit glass look glassy. Alas, the one compromise I had to make for the iPhone is that cube maps are not supported by OpenGL ES 1.1.
In the XCode-generated project, a Cocoa class EAGLView (derived from UIView) sets up the OpenGL context and does all the dirty work. It is straightforward to then wire in this class. The first step is to rename all the .m files in the project to .MM to allow C++ classes to be used in Objective C code. The XCode template creates a Cocoa view class called EAGView, and stubs in all the necessary OpenGL hooks. This is the class in which I bridge to my own C++ implementation.
In EAGLView.h, add the include for MyGLView.h and declare an instance of the class (Listing Two). In Listing Two, the OpenGL ES include files leave EAGL.h out of any C++-only source files (as well as UIKit.h, which is Cocoa GUI code), but you will need the gl.h and potentially the glext.h headers in any source module that calls OpenGL ES functions.
#import <UIKit/UIKit.h> #import <OpenGLES/EAGL.h> #import <OpenGLES/ES1/gl.h> #import <OpenGLES/ES1/glext.h> #include "MyGLView.h" @interface EAGLView : UIView { @private ... MyGLView myGLView; // Instance of our C++ class } @property NSTimeInterval animationInterval; - (void)startAnimation; - (void)stopAnimation; - (void)drawView; @end
In EAGLView.mm, you find the initWithCoder method. Here, after the context creation, you place the code to tell your class to do its initialization, and set up the projection. Because you can't change the window size on the iPhone, you don't need to watch for changes and call ResizeGL again:
myGLView.InitializeGL(); myGLView.ResizeGL(backingWidth, backingHeight); animationInterval = 1.0 / 60.0;
You can also set the desired target frame rate. The buffer swap is always synced and there is never any tearing on the iPhone. Sixty fps will always be the top frame rate you can attain.
In drawView, I call the PaintGL method of the C++ class:
- (void)drawView { [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); // New line myGLView.ResizeGL(backingWidth, backingHeight); // New line myGLView.PaintGL(); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; }
Finally, when the view is destroyed (the application is terminated), dealloc is called and I call ShutdownGL to do any cleanup before the OpenGL context is destroyed:
- (void)dealloc { [self stopAnimation]; myGLView.ShutdownGL(); // Do our shutdown if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; } [context release]; [super dealloc]; }
With a successful bridging of C++ code to the Cocoa Touch rendering framework, you can now create C++-based OpenGL ES rendering code for the iPhone.