O'Reilly logo

iPhone 3D Programming by Philip Rideout

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

HelloArrow with Shaders

In this section you’ll create a new rendering engine that uses ES 2.0. This will show you the immense difference between ES 1.1 and 2.0. Personally I like the fact that Khronos decided against making ES 2.0 backward compatible with ES 1.1; the API is leaner and more elegant as a result.

Thanks to the layered architecture of HelloArrow, it’s easy to add ES 2.0 support while retaining 1.1 functionality for older devices. You’ll be making these four changes:

  1. Add some new source files to the project for the vertex shader and fragment shader.

  2. Update frameworks references if needed.

  3. Change the logic in GLView so that it attempts initWithAPI with ES 2.0.

  4. Create the new RenderingEngine2 class by modifying a copy of RenderingEngine1.

These changes are described in detail in the following sections. Note that step 4 is somewhat artificial; in the real world, you’ll probably want to write your ES 2.0 backend from the ground up.


By far the most significant new feature in ES 2.0 is the shading language. Shaders are relatively small pieces of code that run on the graphics processor, and they are divided into two categories: vertex shaders and fragment shaders. Vertex shaders are used to transform the vertices that you submit with glDrawArrays, while fragment shaders compute the colors for every pixel in every triangle. Because of the highly parallel nature of graphics processors, thousands of shader instances execute simultaneously.

Shaders are written in a C-like language called OpenGL Shading Language (GLSL), but unlike C, you do not compile GLSL programs within Xcode. Shaders are compiled at runtime, on the iPhone itself. Your application submits shader source to the OpenGL API in the form of a C-style string, which OpenGL then compiles to machine code.


Some implementations of OpenGL ES do allow you to compile your shaders offline; on these platforms your application submits binaries rather than strings at runtime. Currently, the iPhone supports shader compilation only at runtime. Its ARM processor compiles the shaders and sends the resulting machine code over to the graphics processor for execution. That little ARM does some heavy lifting!

The first step to upgrading HelloArrow is creating a new project folder for the shaders. Right-click the HelloArrow root node in the Groups & Files pane, and choose AddNew Group. Call the new group Shaders.

Next, right-click the Shaders folder, and choose AddNew file. Select the Empty File template in the Other category. Name it Simple.vert, and add /Shaders after HelloArrow in the location field. You can deselect the checkbox under Add To Targets, because there’s no need to deploy the file to the device. In the next dialog, allow it to create a new directory. Now repeat the procedure with a new file called Simple.frag.

Before showing you the code for these two files, let me explain a little trick. Rather than reading in the shaders with file I/O, you can simply embed them within your C/C++ code through the use of #include. Multiline strings are usually cumbersome in C/C++, but they can be tamed with a sneaky little macro:

#define STRINGIFY(A)  #A

Later in this section, we’ll place this macro definition at the top of the rendering engine source code, right before #including the shaders. The entire shader gets wrapped into a single string—without the use of quotation marks on every line!

Examples 1-13 and 1-14 show the contents of the vertex shader and fragment shader. For brevity’s sake, we’ll leave out the STRINGIFY accouterments in future shader listings, but we’re including them here for clarity.

Example 1-13. Simple.vert

const char* SimpleVertexShader = STRINGIFY(

attribute vec4 Position;
attribute vec4 SourceColor;
varying vec4 DestinationColor;
uniform mat4 Projection;
uniform mat4 Modelview;

void main(void)
    DestinationColor = SourceColor;
    gl_Position = Projection * Modelview * Position;

First the vertex shader declares a set of attributes, varyings, and uniforms. For now you can think of these as connection points between the vertex shader and the outside world. The vertex shader itself simply passes through a color and performs a standard transformation on the position. You’ll learn more about transformations in the next chapter. The fragment shader (Example 1-14) is even more trivial.

Example 1-14. Simple.frag

const char* SimpleFragmentShader = STRINGIFY(

varying lowp vec4 DestinationColor;

void main(void)
    gl_FragColor = DestinationColor;

Again, the varying parameter is a connection point. The fragment shader itself does nothing but pass on the color that it’s given.


Next, make sure all the frameworks in HelloArrow are referencing a 3.1 (or greater) version of the SDK. To find out which version a particular framework is using, right-click it in Xcode’s Groups & Files pane, and select Get Info to look at the full path.


There’s a trick to quickly change all your SDK references by manually modifying the project file. First you need to quit Xcode. Next, open Finder, right-click HelloArrow.xcodeproj, and select Show package contents. Inside, you’ll find a file called project.pbxproj, which you can then open with TextEdit. Find the two places that define SDKROOT, and change them appropriately.


You might recall passing in a version constant when constructing the OpenGL context; this is another place that obviously needs to be changed. In the Classes folder, open GLView.mm, and change this snippet:

m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
    [self release];
    return nil;

m_renderingEngine = CreateRenderer1();

to this:

EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
m_context = [[EAGLContext alloc] initWithAPI:api];

if (!m_context || ForceES1) {
    api = kEAGLRenderingAPIOpenGLES1;
    m_context = [[EAGLContext alloc] initWithAPI:api];

if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
    [self release];
    return nil;

if (api == kEAGLRenderingAPIOpenGLES1) {
    m_renderingEngine = CreateRenderer1();
} else {
    m_renderingEngine = CreateRenderer2();

The previous code snippet creates a fallback path to allow the application to work on older devices while leveraging ES 2.0 on newer devices. For convenience, the ES 1.1 path is used even on newer devices if the ForceES1 constant is enabled. Add this to the top of GLView.mm:

const bool ForceES1 = false;

There’s no need to make any changes to the IRenderingEngine interface, but you do need to add a declaration for the new CreateRenderer2 factory method in IRenderingEngine.hpp:


// Create an instance of the renderer and set up various OpenGL state.
struct IRenderingEngine* CreateRenderer1();
struct IRenderingEngine* CreateRenderer2();

// Interface to the OpenGL ES renderer; consumed by GLView.
struct IRenderingEngine {
    virtual void Initialize(int width, int height) = 0;    
    virtual void Render() const = 0;
    virtual void UpdateAnimation(float timeStep) = 0;
    virtual void OnRotate(DeviceOrientation newOrientation) = 0;
    virtual ~IRenderingEngine() {}

RenderingEngine Implementation

You’re done with the requisite changes to the glue code; now it’s time for the meat. Use Finder to create a copy of RenderingEngine1.cpp (right-click or Control-click RenderingEngine1.cpp and choose Reveal in Finder), and name the new file RenderingEngine2.cpp. Add it to your Xcode project by right-clicking the Classes group and choosing AddExisting Files. Next, revamp the top part of the file as shown in Example 1-15. New or modified lines are shown in bold.

Example 1-15. RenderingEngine2 declaration

#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#include <cmath>
#include <iostream>
#include "IRenderingEngine.hpp"

#define STRINGIFY(A)  #A
#include "../Shaders/Simple.vert"
#include "../Shaders/Simple.frag"

static const float RevolutionsPerSecond = 1;

class RenderingEngine2 : public IRenderingEngine {
    void Initialize(int width, int height);
    void Render() const;
    void UpdateAnimation(float timeStep);
    void OnRotate(DeviceOrientation newOrientation);
    float RotationDirection() const;
    GLuint BuildShader(const char* source, GLenum shaderType) const;
    GLuint BuildProgram(const char* vShader, const char* fShader) const;
    void ApplyOrtho(float maxX, float maxY) const;
    void ApplyRotation(float degrees) const;
    float m_desiredAngle;
    float m_currentAngle;
    GLuint m_simpleProgram;
    GLuint m_framebuffer;
    GLuint m_renderbuffer;

As you might expect, the implementation of Render needs to be replaced. Flip back to Example 1-11 to compare it with Example 1-16.

Example 1-16. Render with OpenGL ES 2.0

void RenderingEngine2::Render() const
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    GLuint positionSlot = glGetAttribLocation(m_simpleProgram, "Position");
    GLuint colorSlot = glGetAttribLocation(m_simpleProgram, "SourceColor");
    GLsizei stride = sizeof(Vertex);
    const GLvoid* pCoords = &Vertices[0].Position[0];
    const GLvoid* pColors = &Vertices[0].Color[0];
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);
    GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex);
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);

As you can see, the 1.1 and 2.0 versions of Render are quite different, but at a high level they basically follow the same sequence of actions.

Framebuffer objects have been promoted from a mere OpenGL extension to the core API. Luckily OpenGL has a very strict and consistent naming convention, so this change is fairly mechanical. Simply remove the OES suffix everywhere it appears. For function calls, the suffix is OES; for constants the suffix is _OES. The constructor is very easy to update:

    glGenRenderbuffers(1, &m_renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);

The only remaining public method that needs to be updated is Initialize, shown in Example 1-17.

Example 1-17. RenderingEngine2 initialization

void RenderingEngine2::Initialize(int width, int height)
    // Create the framebuffer object and attach the color buffer.
    glGenFramebuffers(1, &m_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
    glViewport(0, 0, width, height);
    m_simpleProgram = BuildProgram(SimpleVertexShader, SimpleFragmentShader);
    // Initialize the projection matrix.
    ApplyOrtho(2, 3);
    // Initialize rotation animation state.
    m_currentAngle = m_desiredAngle;

This calls the private method BuildProgram, which in turn makes two calls on the private method BuildShader. In OpenGL terminology, a program is a module composed of several shaders that get linked together. Example 1-18 shows the implementation of these two methods.

Example 1-18. BuildProgram and BuildShader

GLuint RenderingEngine2::BuildProgram(const char* vertexShaderSource,
                                      const char* fragmentShaderSource) const
    GLuint vertexShader = BuildShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fragmentShader = BuildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
    return programHandle;

GLuint RenderingEngine2::BuildShader(const char* source, GLenum shaderType) const
    GLuint shaderHandle = glCreateShader(shaderType);
    glShaderSource(shaderHandle, 1, &source, 0);
    GLint compileSuccess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
    if (compileSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
    return shaderHandle;

You might be surprised to see some console I/O in Example 1-18. This dumps out the shader compiler output if an error occurs. Trust me, you’ll always want to gracefully handle these kind of errors, no matter how simple you think your shaders are. The console output doesn’t show up on the iPhone screen, but it can be seen in Xcode’s Debugger Console window, which is shown via the RunConsole menu. See Figure 1-10 for an example of how a shader compilation error shows up in the console window.

Debugger console

Figure 1-10. Debugger console

Note that the log in Figure 1-10 shows the version of OpenGL ES being used. To include this information, go back to the GLView class, and add the lines in bold:

if (api == kEAGLRenderingAPIOpenGLES1) {
    NSLog(@"Using OpenGL ES 1.1");
    m_renderingEngine = CreateRenderer1();
} else {
    NSLog(@"Using OpenGL ES 2.0");
    m_renderingEngine = CreateRenderer2();

The preferred method of outputting diagnostic information in Objective-C is NSLog, which automatically prefixes your string with a timestamp and follows it with a carriage return. (Recall that Objective-C string objects are distinguished from C-style strings using the @ symbol.)

Return to RenderingEngine2.cpp. Two methods remain: ApplyOrtho and ApplyRotation. Since ES 2.0 does not provide glOrthof or glRotatef, you need to implement them manually, as seen in Example 1-19. (In the next chapter, we’ll create a simple math library to simplify code like this.) The calls to glUniformMatrix4fv provide values for the uniform variables that were declared in the shader source.

Example 1-19. ApplyOrtho and ApplyRotation

void RenderingEngine2::ApplyOrtho(float maxX, float maxY) const
    float a = 1.0f / maxX;
    float b = 1.0f / maxY;
    float ortho[16] = {
        a, 0,  0, 0,
        0, b,  0, 0,
        0, 0, -1, 0,
        0, 0,  0, 1
    GLint projectionUniform = glGetUniformLocation(m_simpleProgram, "Projection");
    glUniformMatrix4fv(projectionUniform, 1, 0, &ortho[0]);

void RenderingEngine2::ApplyRotation(float degrees) const
    float radians = degrees * 3.14159f / 180.0f;
    float s = std::sin(radians);
    float c = std::cos(radians);
    float zRotation[16] = {
        c, s, 0, 0,
       -s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    GLint modelviewUniform = glGetUniformLocation(m_simpleProgram, "Modelview");
    glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]);

Again, don’t be intimidated by the matrix math; I’ll explain it all in the next chapter.

Next, go through the file, and change any remaining occurrences of RenderingEngine1 to RenderingEngine2, including the factory method (and be sure to change the name of that method to CreateRenderer2). This completes all the modifications required to run against ES 2.0. Phew! It’s obvious by now that ES 2.0 is “closer to the metal” than ES 1.1.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required