OpenGL ES in iOS supports both OpenGL ES 1.1, implementing a fixed-function graphics pipeline, and OpenGL ES 2.0, supporting a shader pipeline. OpenGL ES 2.0 is not a superset of OpenGL ES 1.1; the fixed-function capabilities of OpenGL ES 1.1 were removed to provide a streamlined interface to the graphics hardware.
Both OpenGL ES 1.1 and OpenGL ES 2.0 define minimum capabilities that every implementation must support. However, the OpenGL ES specification does not limit a hardware implementation to just those capabilities. An OpenGL ES implementation can extend the minimum capabilities (for example, increasing the maximum size of a texture) or extend the capabilities of OpenGL ES through the OpenGL ES extensions mechanism. Apple uses both mechanisms to offer a variety of capabilities on different iOS-based devices. A later chapter, “Platform Notes,” drills down into these specific capabilities. However, because Apple provides different versions of OpenGL ES, and different capabilities even within the same version, your application must test the capabilities of the device and adjust its behavior to match those capabilities. If your application fails to test the capabilities of OpenGL ES at runtime, it may crash or fail to run, providing a bad experience to the user.
When designing your OpenGL ES application, the first question you must answer is whether your application supports OpenGL ES 1.1, OpenGL ES 2.0, or both.
The fixed-function pipeline of OpenGL ES 1.1 provides good baseline behavior for a 3D graphics pipeline, from transforming and lighting vertices to blending the final pixels with the framebuffer. If you choose to implement an OpenGL ES 2.0 application, you need to duplicate this functionality. OpenGL ES 2.0 is more flexible than OpenGL ES 1.1. Custom vertex and fragment operations that would be difficult or impossible to implement using OpenGL ES 1.1 can be trivially implemented with an OpenGL ES 2.0 shader. Implementing a custom operation in an OpenGL ES 1.1 application often requires multiple rendering passes and complex changes to OpenGL ES state that obscure the intent of the code. As your algorithms grow in complexity, shaders convey those operations more clearly and concisely and with better performance.
Your application should target OpenGL ES 1.1 if you want to support all iPhone and iPod touch devices. Your application should target OpenGL ES 2.0 when you want to take advantage of the rich and expressive power of OpenGL ES 2.0 shaders. You can also implement both in the same application, providing baseline behavior that works on OpenGL ES 1.1 devices, and an enriched experience on devices that support OpenGL ES 2.0.
In iOS, your application decides which version of OpenGL ES to use when it initializes an OpenGL ES rendering context, known as an EAGLContext
object. Your application states which version of OpenGL ES that a particular context uses. For example, to create a context that uses OpenGL ES 1.1, your application would initialize it as shown:
EAGLContext* myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; |
If your application wants to use OpenGL ES 2.0, the code is almost identical:
EAGLContext* myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; |
If a particular implementation of OpenGL ES is not available, initWithAPI:
returns nil
. Your application must test whether the context was successfully initialized before using it.
To support both OpenGL ES 2.0 and OpenGL ES 1.1 in the same application, your application should first attempt to create an OpenGL ES 2.0 rendering context. If that fails, it tries to create an OpenGL ES 1.1 context instead, as shown in Listing 2-1.
Listing 2-1 Supporting OpenGL ES 1.1 and OpenGL ES 2.0 in the same application
EAGLContext* CreateBestEAGLContext() |
{ |
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; |
if (context == nil) |
{ |
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; |
} |
return context; |
} |
Once your application has an initialized context, it can read the API
property of the context to determine which version of OpenGL ES it supports. For example, your application could implement a fixed-function renderer and a shader renderer in classes, sharing a common base class. Your application would instantiate the appropriate renderer at runtime.
Important: OpenGL ES 2.0 removes many functions found in OpenGL ES 1.1 and adds functions that are not available in OpenGL ES 1.1. If your application attempts to call an OpenGL ES 2.0 function on an OpenGL ES 1.1 context (or vice versa), the results are undefined. While some sharing of code is possible between your two rendering paths, you should limit this sharing to calls that work identically between the two versions of OpenGL ES.
Starting in iOS 3.0, your application can include an entry in its information property list to prevent the application from launching if a particular version of OpenGL ES is not available. See Device Support in iOS Application Programming Guide for further instructions on how to add these keys.
If your application supports both OpenGL ES 2.0 and OpenGL ES 1.1, you should not restrict your application to run only on OpenGL ES 2.0 devices.
Whether you’ve chosen to build an OpenGL ES 1.1 or OpenGL ES 2.0 application, the next thing your application needs to do is find out what the OpenGL ES implementation is capable of. Your renderer should make the context the current context and test these capabilities once, caching the results. It can then use this information to tailor the algorithms it uses. For example, depending on the number of texture units available to your application, you might be able to perform a complex algorithm in a single pass, you might need to perform the algorithm in multiple passes, or you might need to choose a simpler algorithm. Since the capabilities of the context do not change once it has been created, your application can test this once and determine which path it to use.
The OpenGL ES specification defines implementation-dependent values that define the limits of what an OpenGL ES implementation is capable of. For example, the maximum size of a texture and the number of texture units are both common implementation-dependent values that an application is expected to check. Each of these values has a minimum value that all conforming implementations are expected to support. If your application’s usage exceeds these minimums, it must check the limit first, and fail gracefully if the implementation cannot provide the limit desired. Your application may need to load smaller textures, disable a rendering feature, or choose a different implementation.
Although the specification provides a comprehensive list of these limitations, a few stand out in most OpenGL applications. Table 2-1 lists values that both OpenGL ES 1.1 and OpenGL ES 2.0 applications should test if they require more than the minimum values in the specification.
Maximum size of the texture | GL_MAX_TEXTURE_SIZE |
Number of depth buffer planes | GL_DEPTH_BITS |
Number of stencil buffer planes | GL_STENCIL_BITS |
Further, OpenGL ES 1.1 applications should always check the number of texture units and the number of available clipping planes, as shown in Table 2-2.
Maximum number of texture units available to the fixed function pipeline | GL_MAX_TEXTURE_UNITS |
Maximum number of clip planes | GL_MAX_CLIP_PLANES |
In an OpenGL ES 2.0 application, the limits on your shaders are the primary area you need to test. All graphics hardware supports limited memory to pass attributes into the vertex and fragment shaders. An OpenGL ES 2.0 implementation is not required to provide a software fallback if your application exceeds this usage, so your application must either keep its usage below the minimums as defined in the specification, or it must check the shader limitations documented in Table 2-3 and use them to choose shaders that are within those limits.
Maximum number of vertex attributes | GL_MAX_VERTEX_ATTRIBS |
Maximum number of uniform vertex vectors | GL_MAX_VERTEX_UNIFORM_VECTORS |
Maximum number of uniform fragment vectors | GL_MAX_FRAGMENT_UNIFORM_VECTORS |
Maximum number of varying vectors | GL_MAX_VARYING_VECTORS |
Maximum number of texture units usable in a vertex shader | GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS |
Maximum number of texture units usable in a fragment shader | GL_MAX_TEXTURE_IMAGE_UNITS |
Any OpenGL ES implementation can extend the capabilities of the API by implementing OpenGL ES extensions. “Platform Notes” details the capabilities supported on each implementation of OpenGL ES.
Your application must check for the existence of any OpenGL ES extension before relying on changes it makes to the OpenGL ES specification. The sole exception is the GL_OES_framebuffer_object extension. Framebuffer objects are available in all OpenGL ES 2.0 implementations; Apple extends all implementations of OpenGL ES 1.1 to include it. Apple relies on the framebuffer extension to provide all framebuffer objects in iOS.
Listing 2-2 provides code you can use to check for the existence of extensions.
Listing 2-2 Checking for OpenGL ES extensions.
BOOL CheckForExtension(NSString *searchName) |
{ |
// For best results, extensionsNames should be stored in your renderer so that it does not |
// need to be recreated on each invocation. |
NSString *extensionsString = [NSString stringWithCString:glGetString(GL_EXTENSIONS) encoding: NSASCIIStringEncoding]; |
NSArray *extensionsNames = [extensionsString componentsSeparatedByString:@" "]; |
return [extensionsNames containsObject: searchName]; |
} |
The debug version of your application should call glGetError
after every OpenGL ES command and flag any error that is returned. Typically, if an error is returned from glGetError
, it means the application is using the API incorrectly.
Note that repeatedly calling glGetError
can significantly degrade the performance of your application. You should not call it in the release version of your application.
Last updated: 2010-07-09