OpenGL ES Template in Xcode

Source code in GL.zip

  1. main.m
  2. Class GLAppDelegate
  3. Class GLViewController
  4. Class EAGLView
  5. GL-Info.plist
  6. Shaders
    1. Shader.vsh vertex
    2. Shader.fsh fragment
  7. Xib files
    1. MainWindow.xib
    2. GLViewController.xib

Create the project

File → New Project…
Choose a template for your new project: OpenGL ES Application

Do not remove the MainWindow.xib file from the project. Do not remove the “Main nib file base name” from the Info.plist file. The DEBUG macro in GLViewController.m is already defined; it will send any error messages to the Xcode Console window. You can see its definition (as the empty string) in
Project → Edit Active Target "GL" → Build → GCC 4.2 - Preprocessing → Preprocessor Macros

Project → Edit Active Target "GL" → General → Linked Libraries
You should have four linked libraries:

  1. Foundation.framework
  2. UIKit.framework
  3. OpenGLES.framework
  4. QuartzCore.framework

The view controller

The view controller is usually created by the application delegate. But this view controller is unarchived from the MainWindow.xib file, leaving the application delegate with little to do except call the startAnimation and stopAnimation methods of the view controller.

The view controller contains an instance variable named view that points to the view it controls. This view is of class EAGLView.

OpenGL ES 1.0 or 2.0?

The view controller creates an EAGLContext. This context is a souped-up version of the humble CGContextRef we used for 2D graphics in Japan. The view controller prefers to create an OpenGL ES 2.0 context, but it will settle for an OpenGL ES 1.0 context. As a C programmer, I have a rage to do as much as possible in a single expresion. I would like to change

	EAGLContext *aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

	if (!aContext)
	{
		aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
	}

	if (!aContext)
		NSLog(@"Failed to create ES context");
	else if (![EAGLContext setCurrentContext:aContext])
		NSLog(@"Failed to set ES context current");
in the awakeFromNib method of the view controller to the following, making the parallelism between 1.0 and 2.0 as conspicuous as possible.
	EAGLContext *aContext;

	if ((aContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2]) == nil
	 && (aContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES1]) == nil) {
		NSLog(@"Failed to create ES context");
	}

	else if (![EAGLContext setCurrentContext: aContext]) {
		NSLog(@"Failed to set ES context current");
	}

On my iOS 4.1, the first call to initWithAPI: produces the following Console output, after which NSLog no longer works.

Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
open$UNIX2003 called from function _ZN4llvm12MemoryBuffer7getFileEPKcPSsx in image libLLVMContainer.dylib.
etc.

Which kind of timer?

When the application delegate calls startAnimation, the view controller will start calling its own drawFrame method 60 times per second. To get the 60 hertz cycle, the view controller will create either a CADisplayLink (as in Pong) or an NSTimer. It would prefer to create a CADisplayLink. As a C programmer, I would like to change

		displayLinkSupported = FALSE;

		// Use of CADisplayLink requires iOS version 3.1 or greater.
		// The NSTimer object is used as fallback when it isn't available.
		NSString *reqSysVer = @"3.1";
		NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
		if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending)
			displayLinkSupported = TRUE;
to
		// Use of CADisplayLink requires iOS version 3.1 or greater.
		// The NSTimer object is used as fallback when it isn't available.

		NSString *reqSysVer = @"3.1";
		NSString *currSysVer = [[UIDevice currentDevice] systemVersion];

		//Let displayLinkSupported be YES if currSysVer >= reqSysVer.
		displayLinkSupported =
			[currSysVer compare: reqSysVer options: NSNumericSearch] != NSOrderedAscending;
in the awakeFromNib method of the view controller.

The drawFrame method

The drawFrame method of the view controller, called 60 times per second, draws the picture. The glClearColor designates the gray background color.

The two arrays in drawFrame

Each pair of GLfloats in the array squareVertices represent a vertex. That’s why drawFrame passes the arguments 2 and GL_FLOAT to the functions glVertexAttribPointer (OpenGL ES 2.0) and glVertexPointer (OpenGL ES 1.1). OpenGL ES puts the origin (0, 0) at the center of the picture, with the X axis pointing right and the Y axis pointing up. The first point in the array is therefore the lower left corner of the square. As we are about to see, it is colored yellow.

Each quartet of GLubytes in the array squareColors represents an rgbα color. That’s why drawFrame passes the arguments 4 and GL_UNSIGNED_BYTE to a second call to glVertexAttribPointer (OpenGL ES 2.0) and to glColorPointer.

We start at the beginning of each array (at element zero) and draw four points. That’s why drawFrame passes the arguments 0 and 4 to glDrawArrays. As a C programmer, I’d rather not count the 4 points in squareVertices myself. I’d rather say

#define D 2	//number of dimensions
#define V (sizeof squareVertices / (D * sizeof squareVertices[0]))	//number of vertices

	glDrawArrays(GL_TRIANGLE_STRIP, 0, V);

The triangle strip

The drawFrame method draws a strip of triangles consisting of two triangles determined by four vertices.

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
Our array squareVertices contains four vertices:
 black 2-----3 purple
       |\    |
       | \   |
       |  \  |
       |   \ |
       |    \|
yellow 0-----1 cyan (a mixture of green and blue)
But we could easily have an array of more of them:
       6-----7
       |\    |
       | \   |
       |  \  |
       |   \ |
       |    \|
       4-----5
       |\    |
       | \   |
       |  \  |
       |   \ |
       |    \|
       2-----3
       |\    |
       | \   |
       |  \  |
       |   \ |
       |    \|
       0-----1

The transY variable in drawFrame

The variables passed to the “vertex shader” function in Shader.vsh are called uniforms. We have one example: the variable transY in drawFrame is passed to the shader, arriving there under the name of translate.

	static float transY = 0.0f;	//angle in radians

Here’s where we plug in the names transY and translate. The array uniforms at the top of GLViewController.m contains one element for each uniform. There is only one uniform (transY), so the array contains only one element. The enumeration UNIFORM_TRANSLATE is the subscript of the element.

// Uniform index.
enum {
	UNIFORM_TRANSLATE,
	NUM_UNIFORMS
};

GLint uniforms[NUM_UNIFORMS];
We put a value into the element at the end of the loadShaders method at the end of GLViewController.h:
	// Get uniform locations.
	uniforms[UNIFORM_TRANSLATE] = glGetUniformLocation(program, "translate");
We use the value of this element in drawFrame:
	// Update uniform value.
	glUniform1f(uniforms[UNIFORM_TRANSLATE], (GLfloat)transY);
	transY += 0.075f;

Compile and load the shaders

Shader.vsh and Shader.fsh are two simple programs that get compiled and linked by the app. As each frame is drawn, Shader.vsh is executed once for each vertex, and Shader.fsh is executed once for each pixel (fragment). They are written in the OpenGL ES Shading Language, which looks a lot like C. Shaders are present only in OpenGL ES 2.0, not 1.1.

The following methods belong to the view controller. The awakeFromNib method calls the loadShaders method, which calls compileShader (twice) and linkProgram.

The view

What kind of layer?

Every view has a property named layer pointing to the view’s layer object. For example, a plain vanilla view has a plain vanilla layer of class CALayer.

	UIView *view = [[UIView alloc] init];
	NSLog(@"view.layer belongs to class %@.", view.layer.class);
[Session started at 2011-05-13 11:54:30 -0400.]
2011-05-13 11:54:31.297 GL[3283:207] view.layer belongs to class CALayer.

But thanks to the layerClass method of class EAGLView in EAGLView.m, an EAGLView object will have a much more elaborate layer of class CAEAGLayer.

The framebuffer contains the renderbuffer.

A renderbuffer is the area of memory where a picture will be drawn. Each renderbuffer has an identifying number given to it by glGenRenderbuffers. The identifying number of our renderbuffer is stored in the colorRenderbuffer instance variable of the view. See the createFramebuffer method of class EAGLView.

Somewhat redundantly, the call to glBindRenderbuffer causes our color renderbuffer to be used as a renderbuffer. The view’s layer is attached to this renderbuffer by calling the renderbufferStorage:fromDrawable: method of the view. Displaying a view will automatically display the view’s layer, and displaying this view’s layer will now automatically display the renderbuffer.

The view gets its width and height from GLViewController.xib. The layer gets its width and height from the view, and the renderbuffer gets its width and height from the layer. The instance variables framebufferWidth and framebufferHeight get their values from the renderbuffer.

It takes more than a renderbuffer to draw a picture: there might also be a “depth buffer” and a “stencil buffer”. The renderbuffer and all the other buffers are contained in one master buffer called the framebuffer. Each framebuffer has an identifying number given to it by glGenFramebuffers. The identifying number of our framebuffer is stored in the defaultFramebuffer instance variable of the view. We insert the renderbuffer into the framebuffer by calling glFramebufferRenderbuffer.

Things to try

  1. The drawFrame method of the view controller drew a square consisting of a GL_TRIANGLE_STRIP consisting of two triangles. The seven possibilities are listed in the documentation for glDrawArrays:
    1. GL_POINTS
    2. GL_LINE_STRIP
    3. GL_LINE_LOOP
    4. GL_LINES
    5. GL_TRIANGLE_STRIP
    6. GL_TRIANGLE_FAN
    7. GL_TRIANGLES
    Create a (squished) red stop sign consisting of a GL_TRIANGLE_FAN consisting of eight triangles. Pass D instead of 2 to glVertexAttribPointer. You might want to rename the arrays.
    #define D 2	//number of dimensions
    
    	static const GLfloat squareVertices[] = {
    		 0.00f,  0.00f,	//right triangle
    		 0.50f, -0.25f,
    		 0.50f,  0.25f,
    
    		 0.25f,  0.50f,	//upper right triangle
    		-0.25f,  0.50f,	//top triangle
    		-0.50f,  0.25f,	//upper left triangle
    		-0.50f, -0.25f,	//left triangle
    		-0.25f, -0.50f,	//lower left triangle
    		 0.25f, -0.50f,	//bottom triangle
    		 0.50f, -0.25f	//lower right triangle
    	};
    
    #define V (sizeof squareVertices / (D * sizeof squareVertices[0]))	//number of vertices
    
    	static const GLubyte squareColors[] = {
    		255,   0,   0, 255,
    		255,   0,   0, 255,
    		255,   0,   0, 255,
    
    		255,   0,   0, 255,
    		255,   0,   0, 255,
    		255,   0,   0, 255,
    		255,   0,   0, 255,
    		255,   0,   0, 255,
    		255,   0,   0, 255,
    		255,   0,   0, 255
    	};
    
    	glDrawArrays(GL_TRIANGLE_FAN, 0, V);