OpenGL ES Programming Guide for iOS

OpenGL ES Programming Guide for iOS
OpenGL ES
Programming Guide for
iOS
Contents
About OpenGL ES 8
At a Glance 8
OpenGL ES Is a Platform-Neutral API Implemented in iOS 9
GLKit Provides a Drawing Surface and Animation Support 9
iOS Supports Alternative Rendering Targets 10
Apps Require Additional Performance Tuning 10
OpenGL ES May Not Be Used in Background Apps 10
OpenGL ES Places Additional Restrictions on Multithreaded Apps 11
How to Use This Document 11
Prerequisites 11
See Also 11
OpenGL ES in iOS 13
Choosing Which OpenGL ES Versions to Support 13
OpenGL ES 3.0 13
OpenGL ES 2.0 14
OpenGL ES 1.1 14
Supporting Multiple Versions of OpenGL ES 14
Verifying OpenGL ES Capabilities 14
Choosing a Rendering Destination 15
Integrating with iOS 16
Implementing a Rendering Engine 16
Debugging and Profiling 16
Configuring OpenGL ES Contexts 17
EAGL Is the iOS Implementation of an OpenGL ES Rendering Context 17
The Current Context Is the Target for OpenGL ES Function Calls 17
Every Context Targets a Specific Version of OpenGL ES 18
An EAGL Sharegroup Manages OpenGL ES Objects for the Context 19
Drawing with OpenGL ES and GLKit 21
A GLKit View Draws OpenGL ES Content on Demand 21
Creating and Configuring a GLKit View 22
Drawing With a GLKit View 23
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
2
Contents
Rendering Using a Delegate Object 24
A GLKit View Controller Animates OpenGL ES Content 26
Understanding the Animation Loop 26
Using a GLKit View Controller 27
Using GLKit to Develop Your Renderer 29
Handling Vector and Matrix Math 29
Migrating from the OpenGL ES 1.1 Fixed-Function Pipeline 29
Loading Texture Data 29
Drawing to Other Rendering Destinations 30
Creating a Framebuffer Object 30
Creating Offscreen Framebuffer Objects 31
Using Framebuffer Objects to Render to a Texture 32
Rendering to a Core Animation Layer 33
Drawing to a Framebuffer Object 35
Rendering on Demand or with an Animation Loop 35
Rendering a Frame 36
Using Multisampling to Improve Image Quality 38
Multitasking, High Resolution, and Other iOS Features 41
Implementing a Multitasking-Aware OpenGL ES App 41
Background Apps May Not Execute Commands on the Graphics Hardware 41
Delete Easily Re-Created Resources Before Moving to the Background 42
Supporting High-Resolution Displays 43
Supporting Multiple Interface Orientations 44
Presenting OpenGL ES Content on External Displays 44
OpenGL ES Design Guidelines 46
How to Visualize OpenGL ES 46
Designing a High-Performance OpenGL ES App 48
Avoid Synchronizing and Flushing Operations 50
Using glFlush Effectively 50
Avoid Querying OpenGL ES State 51
Use OpenGL ES to Manage Your Resources 51
Use Double Buffering to Avoid Resource Conflicts 51
Be Mindful of OpenGL ES State Variables 53
Replace State Changes with OpenGL ES Objects 54
Tuning Your OpenGL ES App 55
General Performance Recommendations 55
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
3
Contents
Test Your App with Xcode 55
Use Xcode and Instruments to Test for OpenGL ES Errors 57
Annotate Your Drawing Code for Informative Debugging and Profiling 58
Redraw Scenes Only When the Scene Data Changes 59
Disable Unused OpenGL ES Features 59
Minimize the Number of Draw Calls 60
Memory Is a Scarce Resource on iOS Devices 60
Do Not Sort Rendered Objects Unless Necessary 61
Simplify Your Lighting Models 61
Avoid Alpha Test and Discard 61
Be Aware of Core Animation Compositing Performance 62
Concurrency and OpenGL ES 63
Identifying Whether You Can Benefit from Concurrency 63
OpenGL ES Restricts Each Context to a Single Thread 64
Strategies for Implementing Concurrency in OpenGL ES Apps 65
Perform OpenGL ES Computations in a Worker Task 65
Use Multiple OpenGL ES Contexts 66
Guidelines for Threading OpenGL ES Apps 66
Best Practices for Working with Vertex Data 67
Simplify Your Models 68
Avoid Storing Constants in Attribute Arrays 69
Use the Smallest Acceptable Types for Attributes 69
Use Interleaved Vertex Data 70
Avoid Misaligned Vertex Data 70
Use Triangle Strips to Batch Vertex Data 71
Use Vertex Buffer Objects to Manage Copying Vertex Data 73
Buffer Usage Hints 75
Consolidate Vertex Array State Changes Using Vertex Array Objects 77
Best Practices for Working with Texture Data 80
Load Textures During Initialization 80
Use the GLKit Framework to Load Texture Data 80
Reduce Texture Memory Usage 82
Compress Textures 82
Use Lower-Precision Color Formats 82
Use Properly Sized Textures 82
Combine Textures into Texture Atlases 83
Use Mipmapping to Reduce Memory Bandwidth Usage 84
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
4
Contents
Use Multitexturing Instead of Multiple Passes 84
Best Practices for Shaders 85
Compile and Link Shaders During Initialization 85
Check for Shader Program Errors When Debugging 85
Use Separate Shader Objects to Speed Compilation and Linking 86
Respect the Hardware Limits on Shaders 87
Use Precision Hints 88
Perform Vector Calculations Lazily 89
Use Uniforms or Constants Instead of Computing Values in a Shader 90
Avoid Branching 90
Eliminate Loops 90
Avoid Computing Array Indices in Shaders 91
Be Aware of Dynamic Texture Lookups 91
Using texturetool to Compress Textures 92
texturetool Parameters 92
Document Revision History 97
Glossary 99
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
5
Figures and Listings
Configuring OpenGL ES Contexts 17
Figure 2-1
Listing 2-1
Listing 2-2
Two contexts sharing OpenGL ES objects 19
Supporting multiple versions of OpenGL ES in the same app 18
Creating two contexts with a common sharegroup 20
Drawing with OpenGL ES and GLKit 21
Figure 3-1
Figure 3-2
Listing 3-1
Listing 3-2
Listing 3-3
Listing 3-4
Rendering OpenGL ES content with a GLKit view 22
The animation loop 26
Configuring a GLKit view 23
Example drawing method for a GLKit view 23
Choosing a renderer class based on hardware features 25
Using a GLKit view and view controller to draw and animate OpenGL ES content 27
Drawing to Other Rendering Destinations 30
Figure 4-1
Figure 4-2
Figure 4-3
Figure 4-4
Listing 4-1
Listing 4-2
Listing 4-3
Listing 4-4
Listing 4-5
Framebuffer with color and depth renderbuffers 30
Core Animation shares the renderbuffer with OpenGL ES 33
iOS OpenGL Rendering Steps 36
How multisampling works 39
Creating and starting a display link 35
Clear framebuffer attachments 36
Discarding the depth framebuffer 37
Presenting the finished frame 38
Creating the multisample buffer 39
OpenGL ES Design Guidelines 46
Figure 6-1
Figure 6-2
Figure 6-3
Figure 6-4
Figure 6-5
Listing 6-1
OpenGL ES graphics pipeline 47
OpenGL client-server architecture 47
App model for managing resources 48
Single-buffered texture data 52
Double-buffered texture data 53
Disabling state variables on OpenGL ES 1.1 54
Tuning Your OpenGL ES App 55
Figure 7-1
Xcode Frame Debugger before and after adding debug marker groups 58
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
6
Figures and Listings
Listing 7-1
Listing 7-2
Using the EXT_debug_marker extension to annotate drawing commands 58
Using the EXT_debug_label extension to annotate OpenGL ES objects 59
Best Practices for Working with Vertex Data 67
Figure 9-1
Figure 9-2
Figure 9-3
Figure 9-4
Figure 9-5
Figure 9-6
Figure 9-7
Listing 9-1
Listing 9-2
Listing 9-3
Listing 9-4
Listing 9-5
Listing 9-6
Conversion of attribute data to shader variables 67
Interleaved memory structures place all data for a vertex together in memory 70
Use multiple vertex structures when some data is used differently 70
Align Vertex Data to avoid additional processing 71
Triangle strip 71
Use degenerate triangles to merge triangle strips 72
Vertex array object configuration 78
Using primitive restart in OpenGL ES 3.0 72
Submitting vertex data to a shader program 73
Creating vertex buffer objects 74
Drawing using Vertex Buffer Objects 74
Drawing a model with multiple vertex buffer objects 76
Configuring a vertex array object 78
Best Practices for Working with Texture Data 80
Listing 10-1
Loading a two-dimensional texture from a file 81
Best Practices for Shaders 85
Listing 11-1
Listing 11-2
Listing 11-3
Listing 11-4
Listing 11-5
Listing 11-6
Listing 11-7
Read shader compile/link logs only in development builds 85
Compiling and using shaders with the EXT_separate_shader_objects extension 86
Low precision is acceptable for fragment color 88
Poor use of vector operators 89
Proper use of vector operations 89
Specifying a write mask 89
Dependent Texture Read 91
Using texturetool to Compress Textures 92
Listing A-1
Listing A-2
Listing A-3
Encoding options 93
Encoding images into the PVRTC compression format 95
Encoding images into the PVRTC compression format while creating a preview 96
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
7
About OpenGL ES
Important: This is a preliminary document for an API or technology in development. Although this document
has been reviewed for technical accuracy, it is not final. This Apple confidential information is for use only
by registered members of the applicable Apple Developer program. Apple is supplying this confidential
information to help you plan for the adoption of the technologies and programming interfaces described
herein. This information is subject to change, and software implemented according to this document should
be tested with final operating system software and final documentation. Newer versions of this document
may be provided with future seeds of the API or technology.
The Open Graphics Library (OpenGL) is used for visualizing 2D and 3D data. It is a multipurpose open-standard
graphics library that supports applications for 2D and 3D digital content creation, mechanical and architectural
design, virtual prototyping, flight simulation, video games, and more. You use OpenGL to configure a 3D
graphics pipeline and submit data to it. Vertices are transformed and lit, assembled into primitives, and rasterized
to create a 2D image. OpenGL is designed to translate function calls into graphics commands that can be sent
to underlying graphics hardware. Because this underlying hardware is dedicated to processing graphics
commands, OpenGL drawing is typically very fast.
OpenGL for Embedded Systems (OpenGL ES) is a simplified version of OpenGL that eliminates redundant
functionality to provide a library that is both easier to learn and easier to implement in mobile graphics
hardware.
At a Glance
OpenGL ES allows an app to harness the power of the underlying graphics processor. The GPU on iOS devices
can perform sophisticated 2D and 3D drawing, as well as complex shading calculations on every pixel in the
final image. You should use OpenGL ES if the design requirements of your app call for the most direct and
comprehensive access possible to GPU hardware. Typical clients for OpenGL ES include video games and
simulations that present 3D graphics.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
8
About OpenGL ES
At a Glance
OpenGL ES is a low-level, hardware-focused API. Though it provides the most powerful and flexible graphics
processing tools, it also has a steep learning curve and a significant effect on the overall design of your app.
For apps that require high-performance graphics for more specialized uses, iOS provides several higher-level
frameworks:
●
The Sprite Kit framework provides a hardware-accelerated animation system optimized for creating 2D
games. (See Sprite Kit Programming Guide .)
●
The Core Image framework provides real-time filtering and analysis for still and video images. (See Core
Image Programming Guide .)
●
Core Animation provides the hardware-accelerated graphics rendering and animation infrastructure for
all iOS apps, as well as a simple declarative programming model that makes it simple to implement
sophisticated user interface animations. (See Core Animation Programming Guide .)
●
You can add animation, physics-based dynamics, and other special effects to Cocoa Touch user interfaces
using features in the UIKit framework.
OpenGL ES Is a Platform-Neutral API Implemented in iOS
Because OpenGL ES is a C-based API, it is extremely portable and widely supported. As a C API, it integrates
seamlessly with Objective-C Cocoa Touch apps. The OpenGL ES specification does not define a windowing
layer; instead, the hosting operating system must provide functions to create an OpenGL ES rendering context,
which accepts commands, and a framebuffer, where the results of any drawing commands are written to.
Working with OpenGL ES on iOS requires using iOS classes to set up and present a drawing surface and using
platform-neutral API to render its contents.
Relevant Chapters: “OpenGL ES in iOS” (page 13), “Configuring OpenGL ES Contexts” (page 17)
GLKit Provides a Drawing Surface and Animation Support
Views and view controllers, defined by the UIKit framework, control the presentation of visual content on iOS.
The GLKit framework provides OpenGL ES–aware versions of these classes. When you develop an OpenGL ES
app, you use a GLKView object to render your OpenGL ES content. You can also use a GLKViewController
object to manage your view and support animating its contents.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
9
About OpenGL ES
At a Glance
Relevant Chapters: “Drawing with OpenGL ES and GLKit” (page 21)
iOS Supports Alternative Rendering Targets
Besides drawing content to fill an entire screen or part of a view hierarchy, you can also use OpenGL ES
framebuffer objects for other rendering strategies. iOS implements standard OpenGL ES framebuffer objects,
which you can use for rendering to an offscreen buffer or to a texture for use elsewhere in an OpenGL ES scene.
In addition, OpenGL ES on iOS supports rendering to a Core Animation layer (the CAEAGLLayer class), which
you can then combine with other layers to build your app’s user interface or other visual displays.
Relevant Chapters: “Drawing to Other Rendering Destinations” (page 30)
Apps Require Additional Performance Tuning
Graphics processors are parallelized devices optimized for graphics operations. To get great performance in
your app, you must carefully design your app to feed data and commands to OpenGL ES so that the graphics
hardware runs in parallel with your app. A poorly tuned app forces either the CPU or the GPU to wait for the
other to finish processing commands.
You should design your app to efficiently use the OpenGL ES API. Once you have finished building your app,
use Instruments to fine tune your app’s performance. If your app is bottlenecked inside OpenGL ES, use the
information provided in this guide to optimize your app’s performance.
Xcode provides tools to help you improve the performance of your OpenGL ES apps.
Relevant Chapters: “OpenGL ES Design Guidelines” (page 46), “Best Practices for Working with
Vertex Data” (page 67), “Best Practices for Working with Texture Data” (page 80), “Best Practices for
Shaders” (page 85), “Tuning Your OpenGL ES App” (page 55)
OpenGL ES May Not Be Used in Background Apps
Apps that are running in the background may not call OpenGL ES functions. If your app accesses the graphics
processor while it is in the background, it is automatically terminated by iOS. To avoid this, your app should
flush any pending commands previously submitted to OpenGL ES prior to being moved into the background
and avoid calling OpenGL ES until it is moved back to the foreground.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
10
About OpenGL ES
How to Use This Document
Relevant Chapters: “Multitasking, High Resolution, and Other iOS Features” (page 41)
OpenGL ES Places Additional Restrictions on Multithreaded Apps
Designing apps to take advantage of concurrency can be useful to help improve your app’s performance. If
you intend to add concurrency to an OpenGL ES app, you must ensure that it does not access the same context
from two different threads at the same time.
Relevant Chapters: “Concurrency and OpenGL ES” (page 63)
How to Use This Document
Begin by reading “OpenGL ES in iOS” (page 13), which provides an overview of how OpenGL ES integrates
into iOS. Read the remaining chapters in order.
Experienced iOS developers should focus on reading “Drawing with OpenGL ES and GLKit” (page 21) to learn
new details about how to integrate OpenGL ES into your app, and “Multitasking, High Resolution, and Other
iOS Features” (page 41) for important caveats about using OpenGL ES in an iOS app. Then, read “OpenGL ES
Design Guidelines” (page 46) to dig deeper into how to design efficient OpenGL ES apps.
Unless otherwise noted, OpenGL ES code examples in this book target OpenGL ES 2.0. You may need to make
changes to use these code examples with other OpenGL ES versions.
Prerequisites
Before attempting use OpenGL ES, you should already be familiar with general iOS app architecture. See Start
Developing iOS Apps Today .
This document is not a complete tutorial or a reference for the OpenGL ES API. To learn more about creating
OpenGL ES apps, consult the references below.
See Also
OpenGL ES is an open standard defined by the Khronos Group. For more information about the OpenGL ES
standard, please consult their web page at http://www.khronos.org/opengles/.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
11
About OpenGL ES
See Also
●
OpenGL® ES 2.0 Programming Guide , published by Addison-Wesley, provides a comprehensive introduction
to OpenGL ES concepts.
●
OpenGL® Shading Language, Third Edition , also published by Addison-Wesley, provides many shading
algorithms useable in your OpenGL ES app. You may need to modify some of these algorithms to run
efficiently on mobile graphics processors.
●
OpenGL ES API Registry is the official repository for the OpenGL ES specifications, the OpenGL ES shading
language specifications, and documentation for OpenGL ES extensions.
●
OpenGL ES 1.1 Reference Pages provides a complete reference to the OpenGL ES 1.1 specification, indexed
alphabetically.
●
OpenGL ES 2.0 Reference Pages provides a complete reference to the OpenGL ES 2.0 specification, indexed
alphabetically.
●
OpenGL ES 3.0 Reference Pages provides a complete reference to the OpenGL ES 3.0 specification, indexed
alphabetically.
●
OpenGL ES Framework Reference describes the platform-specific functions and classes provided by Apple
to integrate OpenGL ES into iOS.
●
iOS Device Compatibility Reference provides more detailed information on the hardware and software
features available to your app.
●
GLKit Framework Reference describes a framework provided by Apple to make it easier to develop
OpenGL ES 2.0 and 3.0 apps.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
12
OpenGL ES in iOS
The OpenGL ES specification defines a platform-neutral API for using GPU hardware to render graphics. Platforms
implementing OpenGL ES provide a rendering context for executing OpenGL ES commands, framebuffers to
hold rendering results, and one or more rendering destinations that present the contents of a framebuffer for
display. In iOS, the EAGLContext class implements a rendering context. iOS provides only one type of
framebuffer, the OpenGL ES framebuffer object, and the GLKView and CAEAGLLayer classes implement
rendering destinations.
Building an OpenGL ES app in iOS requires several considerations, some of which are generic to OpenGL ES
programming and some of which are specific to iOS. Follow this checklist and the detailed sections below to
get started:
1.
Determine which version(s) of OpenGL ES have the right feature set for your app, and create an OpenGL ES
context.
2.
Verify at runtime that the device supports the OpenGL ES capabilities you want to use.
3.
Choose where to render your OpenGL ES content.
4.
Make sure your app runs correctly in iOS.
5.
Implement your rendering engine.
6.
Use Xcode and Instruments to debug your OpenGL ES app and tune it for optimal performance .
Choosing Which OpenGL ES Versions to Support
A critical question you must answer is whether your app should support OpenGL ES 3.0, OpenGL ES 2.0,
OpenGL ES 1.1, or multiple versions. You should target the version or versions of OpenGL ES that support the
features and devices most relevant to your app. To learn about creating contexts for the versions of OpenGL ES
you plan to support, read “Configuring OpenGL ES Contexts” (page 17).
OpenGL ES 3.0
OpenGL ES 3.0 is new in iOS 7 and adds a number of new features, including:
●
Multiple render targets and transform feedback to enable deferred rendering and other graphics algorithms
previously only available in desktop OpenGL
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
13
OpenGL ES in iOS
Verifying OpenGL ES Capabilities
●
OpenGL Shading Language ES 3.0 and integer textures, buffers and operations to enable general-purpose
GPU computing
●
New texture, buffer, and attribute formats for optimizing memory footprint and bandwidth
OpenGL ES 3.0 is only available on some iOS devices.
OpenGL ES 2.0
OpenGL ES 2.0 is based on programmable shaders and is available on all devices supported by iOS 5 or later.
Although the above features are available only on devices supporting OpenGL ES 3.0, iOS provides OpenGL ES
2.0 extensions that support many other features otherwise only available in OpenGL ES 3.0.
OpenGL ES 1.1
OpenGL ES 1.1 provides only a basic fixed-function graphics pipeline.
Supporting Multiple Versions of OpenGL ES
If your app uses OpenGL ES 3.0 to take advantage of the more powerful graphics processors found on newer
iOS devices, you must also provide an OpenGL ES 2.0 rendering option for other devices supported by iOS 7.
If you are maintaining an OpenGL ES 1.1 app, you should consider updating your code for newer OpenGL ES
versions. To learn how the GLKit framework can assist you in this transition, read “Using GLKit to Develop Your
Renderer” (page 29).
Verifying OpenGL ES Capabilities
The iOS Device Compatibility Reference summarizes the capabilities and extensions available on shipping iOS
devices. However, to allow your app to run on as many devices and iOS versions as possible, your app should
always query the OpenGL ES implementation for its capabilities at runtime.
To determine implementation specific limits such as the maximum texture size or maximum number of vertex
attributes, look up the value for the corresponding token (such as MAX_TEXTURE_SIZE or
MAX_VERTEX_ATTRIBS, as found in the gl.h header) using the appropriate glGet function for its data type.
To check for OpenGL ES 3.0 extensions, use the glGetIntegerv and glGetStringi functions as in the
following code example:
BOOL CheckForExtension(NSString *searchName)
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
14
OpenGL ES in iOS
Choosing a Rendering Destination
{
// Create a set containing all extension names.
// (For better performance, create the set only once and cache it for future
use.)
int max = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &max);
NSMutableSet *extensions = [NSMutableSet set];
for (int i = 0; i < max; i++)
{
[extensions addObject: @( (char *)glGetStringi(GL_EXTENSIONS, i) )];
}
return [extensions containsObject: searchName];
}
To check for OpenGL ES 1.1 and 2.0 extensions, call glGetString(GL_EXTENSIONS) to get a space-delimited
list of all extension names.
Choosing a Rendering Destination
In iOS, a framebuffer object stores the results of drawing commands. (iOS does not implement
window-system-provided framebuffers.) You can use the contents of a framebuffer object in multiple ways:
●
The GLKit framework provides a view that draws OpenGL ES content and manages its own framebuffer
object, and a view controller that supports animating OpenGL ES content. Use these classes to create full
screen views or to fit your OpenGL ES content into a UIKit view hierarchy. To learn about these classes,
read “Drawing with OpenGL ES and GLKit” (page 21).
●
The CAEAGLLayer class provides a way to draw OpenGL ES content as part of a Core Animation layer
composition. You must create your own framebuffer object when using this class.
●
As with any OpenGL ES implementation, you can also use framebuffers for offscreen graphics processing
or rendering to a texture for use elsewhere in the graphics pipeline. With OpenGL ES 3.0, offscreen buffers
can be used in rendering algorithms that utilize multiple render targets.
To learn about rendering to an offscreen buffer, a texture, or a Core Animation layer, read “Drawing to Other
Rendering Destinations” (page 30).
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
15
OpenGL ES in iOS
Integrating with iOS
Integrating with iOS
iOS apps support multitasking by default, but handling this feature correctly in an OpenGL ES app requires
additional consideration. Improper use of OpenGL ES can result in your app being killed by the system when
in the background.
Many iOS devices include high-resolution displays, so your app should support multiple display sizes and
resolutions.
To learn about supporting these and other iOS features, read “Multitasking, High Resolution, and Other iOS
Features” (page 41).
Implementing a Rendering Engine
There are many possible strategies for designing your OpenGL ES drawing code, the full details of which are
beyond the scope of this document. Many aspects of rendering engine design are generic to all implementations
of OpenGL and OpenGL ES.
To learn about design considerations important for iOS devices, read “OpenGL ES Design Guidelines” (page
46) and “Concurrency and OpenGL ES” (page 63).
Debugging and Profiling
Xcode and Instruments provide a number of tools for tracking down rendering problems and analyzing
OpenGL ES performance in your app.
To learn more about solving problems and improving performance in your OpenGL ES app, read “Tuning Your
OpenGL ES App” (page 55).
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
16
Configuring OpenGL ES Contexts
Every implementation of OpenGL ES provides a way to create rendering contexts to manage the state required
by the OpenGL ES specification. By placing this state in a context, multiple apps can easily share the graphics
hardware without interfering with the other’s state.
This chapter details how to create and configure contexts on iOS.
EAGL Is the iOS Implementation of an OpenGL ES Rendering Context
Before your app can call any OpenGL ES functions, it must initialize an EAGLContext object. The EAGLContext
class also provides methods used to integrate OpenGL ES content with Core Animation.
The Current Context Is the Target for OpenGL ES Function Calls
Every thread in an iOS app has a current context; when you call an OpenGL ES function, this is the context
whose state is changed by the call. The thread maintains a strong reference to the EAGLContext object.
To set a thread’s current context, call the EAGLContext class method setCurrentContext: when executing
on that thread.
[EAGLContext setCurrentContext: myContext];
Call the EAGLContext class method currentContext to retrieve a thread’s current context.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
17
Configuring OpenGL ES Contexts
Every Context Targets a Specific Version of OpenGL ES
Note: If your app actively switches between two or more contexts on the same thread, call the
glFlush function before setting a new context as the current context. This ensures that previously
submitted commands are delivered to the graphics hardware in a timely fashion.
Every Context Targets a Specific Version of OpenGL ES
An EAGLContext object supports only one version of OpenGL ES. For example, code written for OpenGL ES
1.1 is not compatible with an OpenGL ES 2.0 or 3.0 context. Code using core OpenGL ES 2.0 features is compatible
with a OpenGL ES 3.0 context, and code designed for OpenGL ES 2.0 extensions can often be used in an
OpenGL ES 3.0 context with minor changes. Many new OpenGL ES 3.0 features and increased hardware
capabilities require an OpenGL ES 3.0 context.
Your app decides which version of OpenGL ES to support when it creates and initializes the EAGLContext
object. If the device does not support the requested version of OpenGL ES, the initWithAPI: method returns
nil. Your app must test to ensure that a context was initialized successfully before using it.
To support multiple versions of OpenGL ES as rendering options in your app, you should first attempt to
initialize a rendering context of the newest version you want to target. If the returned object is nil, initialize
a context of an older version instead. Listing 2-1 demonstrates how to do this.
Listing 2-1
Supporting multiple versions of OpenGL ES in the same app
EAGLContext* CreateBestEAGLContext()
{
EAGLContext *context = [[EAGLContext alloc]
initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (context == nil) {
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
return context;
}
A context’s API property states which version of OpenGL ES the context supports. Your app would test the
context’s API property and use it to choose the correct rendering path. A common pattern for implementing
this is to create a class for each rendering path; your app tests the context and creates a renderer once, on
initialization.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
18
Configuring OpenGL ES Contexts
An EAGL Sharegroup Manages OpenGL ES Objects for the Context
An EAGL Sharegroup Manages OpenGL ES Objects for the Context
Although the context holds the OpenGL ES state, it does not directly manage OpenGL ES objects. Instead,
OpenGL ES objects are created and maintained by an EAGLSharegroup object. Every context contains an
EAGLSharegroup object that it delegates object creation to.
The advantage of a sharegroup becomes obvious when two or more contexts refer to the same sharegroup,
as shown in Figure 2-1. When multiple contexts are connected to a common sharegroup, OpenGL ES objects
created by any context are available on all contexts; if you bind to the same object identifier on another context
than the one that created it, you reference the same OpenGL ES object. Resources are often scarce on mobile
devices; creating multiple copies of the same content on multiple contexts is wasteful. Sharing common
resources makes better use of the available graphics resources on the device.
A sharegroup is an opaque object; it has no methods or properties that your app can call. Contexts that use
the sharegroup object keep a strong reference to it.
Figure 2-1
Two contexts sharing OpenGL ES objects
Sharegroups are most useful under two specific scenarios:
●
When most of the resources shared between the contexts are unchanging.
●
When you want your app to be able to create new OpenGL ES objects on a thread other than the main
thread for the renderer. In this case, a second context runs on a separate thread and is devoted to fetching
data and creating resources. After the resource is loaded, the first context can bind to the object and use
it immediately. The GLKTextureLoader class uses this pattern to provide asynchronous texture loading.
To create multiple contexts that reference the same sharegroup, the first context is initialized by calling
initWithAPI:; a sharegroup is automatically created for the context. The second and later contexts are
initialized to use the first context’s sharegroup by calling the initWithAPI:sharegroup: method instead.
Listing 2-2 shows how this would work. The first context is created using the convenience function defined in
Listing 2-1 (page 18). The second context is created by extracting the API version and sharegroup from the
first context.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
19
Configuring OpenGL ES Contexts
An EAGL Sharegroup Manages OpenGL ES Objects for the Context
Important: All contexts associated with the same sharegroup must use the same version of the OpenGL ES
API as the initial context.
Listing 2-2
Creating two contexts with a common sharegroup
EAGLContext* firstContext = CreateBestEAGLContext();
EAGLContext* secondContext = [[EAGLContext alloc] initWithAPI:[firstContext API]
sharegroup: [firstContext sharegroup]];
It is your app’s responsibility to manage state changes to OpenGL ES objects when the sharegroup is shared
by multiple contexts. Here are the rules:
●
Your app may access the object across multiple contexts simultaneously provided the object is not being
modified.
●
While the object is being modified by commands sent to a context, the object must not be read or modified
on any other context.
●
After an object has been modified, all contexts must rebind the object to see the changes. The contents
of the object are undefined if a context references it before binding it.
Here are the steps your app should follow to update an OpenGL ES object:
1.
Call glFlush on every context that may be using the object.
2.
On the context that wants to modify the object, call one or more OpenGL ES functions to change the
object.
3.
Call glFlush on the context that received the state-modifying commands.
4.
On every other context, rebind the object identifier.
Note: Another way to share objects is to use a single rendering context, but multiple destination
framebuffers. At rendering time, your app binds the appropriate framebuffer and renders its frames
as needed. Because all of the OpenGL ES objects are referenced from a single context, they see the
same OpenGL ES data. This pattern uses less resources, but is only useful for single-threaded apps
where you can carefully control the state of the context.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
20
Drawing with OpenGL ES and GLKit
The GLKit framework provides view and view controller classes that eliminate the setup and maintenance code
that would otherwise be required for drawing and animating OpenGL ES content. The GLKView class manages
OpenGL ES infrastructure to provide a place for your drawing code, and the GLKViewController class
provides a rendering loop for smooth animation of OpenGL ES content in a GLKit view. These classes extend
the standard UIKit design patterns for drawing view content and managing view presentation. As a result, you
can focus your efforts primarily on your OpenGL ES rendering code and get your app up and running quickly.
The GLKit framework also provides other features to ease OpenGL ES 2.0 and 3.0 development.
A GLKit View Draws OpenGL ES Content on Demand
The GLKView class provides an OpenGL ES–based equivalent of the standard UIView drawing cycle. A UIView
instance automatically configures its graphics context so that your drawRect: implementation need only
perform Quartz 2D drawing commands, and a GLKView instance automatically configures itself so that your
drawing method need only perform OpenGL ES drawing commands. The GLKView class provides this
functionality by maintaining a framebuffer object that holds the results of your OpenGL ES drawing commands,
and then automatically presents them to Core Animation once your drawing method returns.
Like a standard UIKit view, a GLKit view renders its content on demand. When your view is first displayed, it
calls your drawing method—Core Animation caches the rendered output and displays it whenever your view
is shown. When you want to change the contents of your view, call its setNeedsDisplay method and the
view again calls your drawing method, caches the resulting image, and presents it on screen. This approach
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
21
Drawing with OpenGL ES and GLKit
A GLKit View Draws OpenGL ES Content on Demand
is useful when the data used to render an image changes infrequently or only in response to user action. By
rendering new view contents only when you need to, you conserve battery power on the device and leave
more time for the device to perform other actions.
Figure 3-1
Rendering OpenGL ES content with a GLKit view
Creating and Configuring a GLKit View
You can create and configure a GLKView object either programmatically or using Interface Builder. Before you
can use it for drawing, you must associate it with an EAGLContext object (see “Configuring OpenGL ES
Contexts” (page 17)).
●
When creating a view programmatically, first create a context and then pass it to the view’s
initWithFrame:context: method.
●
After loading a view from a storyboard, create a context and set it as the value of the view’s context
property.
A GLKit view automatically creates and configures its own OpenGL ES framebuffer object and renderbuffers.
You control the attributes of these objects using the view’s drawable properties, as illustrated in Listing 3-1. If
you change the size, scale factor, or drawable properties of a GLKit view, it automatically deletes and re-creates
the appropriate framebuffer objects and renderbuffers the next time its contents are drawn.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
22
Drawing with OpenGL ES and GLKit
A GLKit View Draws OpenGL ES Content on Demand
Listing 3-1
Configuring a GLKit view
- (void)viewDidLoad
{
[super viewDidLoad];
// Create an OpenGL ES context and assign it to the view loaded from storyboard
GLKView *view = (GLKView *)self.view;
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
// Configure renderbuffers created by the view
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
// Enable multisampling
view.drawableMultisample = GLKViewDrawableMultisample4X;
}
You can enable multisampling for a GLKView instance using its drawableMultisample property. Multisampling
is a form of antialiasing that smooths jagged edges, improving image quality in most 3D apps at the cost of
using more memory and fragment processing time—if you enable multisampling, always test your app’s
performance to ensure that it remains acceptable.
Drawing With a GLKit View
Figure 3-1 (page 22) outlines the three steps for drawing OpenGL ES content: preparing OpenGL ES
infrastructure, issuing drawing commands, and presenting the rendered content to Core Animation for display.
The GLKView class implements the first and third steps. For the second step, you implement a drawing method
like the example in Listing 3-2.
Listing 3-2
Example drawing method for a GLKit view
- (void)drawRect:(CGRect)rect
{
// Clear the framebuffer
glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
23
Drawing with OpenGL ES and GLKit
Rendering Using a Delegate Object
// Draw using previously configured texture, shader, uniforms, and vertex array
glBindTexture(GL_TEXTURE_2D, _planetTexture);
glUseProgram(_diffuseShading);
glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0,
_modelViewProjectionMatrix.m);
glBindVertexArrayOES(_planetMesh);
glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT);
}
Note: The glClear function hints to OpenGL ES that any existing framebuffer contents can be
discarded, avoiding costly memory operations to load the previous contents into memory. To ensure
optimal performance, you should always call this function before drawing.
The GLKView class is able to provide a simple interface for OpenGL ES drawing because it manages the standard
parts of the OpenGL ES rendering process:
●
●
Before invoking your drawing method, the view:
●
Makes its EAGLContext object the current context
●
Creates a framebuffer object and renderbuffers based on its current size, scale factor, and drawable
properties (if needed)
●
Binds the framebuffer object as the current destination for drawing commands
●
Sets the OpenGL ES viewport to match the framebuffer size
After your drawing method returns, the view:
●
Resolves multisampling buffers (if multisampling is enabled)
●
Discards renderbuffers whose contents are no longer needed
●
Presents renderbuffer contents to Core Animation for caching and display
Rendering Using a Delegate Object
Many OpenGL ES apps implement rendering code in a custom class. An advantage of this approach is that it
allows you to easily support multiple rendering algorithms by defining a different renderer class for each.
Rendering algorithms that share common functionality can inherit it from a superclass. For example, you might
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
24
Drawing with OpenGL ES and GLKit
Rendering Using a Delegate Object
use different renderer classes to support both OpenGL ES 2.0 and 3.0 (see “Configuring OpenGL ES
Contexts” (page 17)). Or you might use them to customize rendering for better image quality on devices with
more powerful hardware.
GLKit is well suited to this approach—you can make your renderer object the delegate of a standard GLKView
instance. Instead of subclassing GLKView and implementing the drawRect: method, your renderer class
adopts the GLKViewDelegate protocol and implements the glkView:drawInRect: method. Listing 3-3
demonstrates choosing a renderer class based on hardware features at app launch time.
Listing 3-3
Choosing a renderer class based on hardware features
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Create a context so we can test for features
EAGLContext *context = [[EAGLContext alloc]
initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:context];
// Choose a rendering class based on device features
GLint maxTextureSize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
if (maxTextureSize > 2048)
self.renderer = [[MyBigTextureRenderer alloc] initWithContext:context];
else
self.renderer = [[MyRenderer alloc] initWithContext:context];
// Make the renderer the delegate for the view loaded from the main storyboard
GLKView *view = (GLKView *)self.window.rootViewController.view;
view.delegate = self.renderer;
// Give the OpenGL ES context to the view so it can draw
view.context = context;
return YES;
}
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
25
Drawing with OpenGL ES and GLKit
A GLKit View Controller Animates OpenGL ES Content
A GLKit View Controller Animates OpenGL ES Content
By default, a GLKView object renders its contents on demand. That said, a key advantage to drawing with
OpenGL ES is its ability to use graphics processing hardware for continuous animation of complex scenes—apps
such as games and simulations rarely present static images. For these cases, the GLKit framework provides a
view controller class that maintains an animation loop for the GLKView object it manages. This loop follows
a design pattern common in games and simulations, with two phases: update and display. Figure 3-2 shows
a simplified example of an animation loop.
Figure 3-2
The animation loop
Understanding the Animation Loop
For the update phase, the view controller calls its own update method (or its delegate’s
glkViewControllerUpdate: method). In this method, you should prepare for drawing the next frame. For
example, a game might use this method to determine the positions of player and enemy characters based on
input events received since the last frame, and a scientific visualization might use this method to run a step
of its simulation. If you need timing information to determine your app’s state for the next frame, use one of
the view controller’s timing properties such as the timeSinceLastUpdate property. In Figure 3-2, the update
phase increments an angle variable and uses it to calculate a transformation matrix.
For the display phase, the view controller calls its view’s display method, which in turn calls your drawing
method. In your drawing method, you submit OpenGL ES drawing commands to the GPU to render your
content. For optimal performance, your app should modify OpenGL ES objects at the start of rendering a new
frame, and submit drawing commands afterward. In Figure 3-2, the display phase sets a uniform variable in a
shader program to the matrix calculated in the update phase, and then submits a drawing command to render
new content.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
26
Drawing with OpenGL ES and GLKit
A GLKit View Controller Animates OpenGL ES Content
The animation loop alternates between these two phases at the rate indicated by the view controller’s
framesPerSecond property. You can use the preferredFramesPerSecond property to set a desired frame
rate—to optimize performance for the current display hardware, the view controller automatically chooses an
optimal frame rate close to your preferred value.
Important: For best results, choose a frame rate your app can consistently achieve. A smooth, consistent
frame rate produces a more pleasant user experience than a frame rate that varies erratically.
Using a GLKit View Controller
Listing 3-4 demonstrates a typical strategy for rendering animated OpenGL ES content using a
GLKViewController subclass and GLKView instance.
Listing 3-4
Using a GLKit view and view controller to draw and animate OpenGL ES content
@implementation PlanetViewController // subclass of GLKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Create an OpenGL ES context and assign it to the view loaded from storyboard
GLKView *view = (GLKView *)self.view;
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
// Set animation frame rate
self.preferredFramesPerSecond = 60;
// Not shown: load shaders, textures and vertex arrays, set up projection
matrix
[self setupGL];
}
- (void)update
{
_rotation += self.timeSinceLastUpdate * M_PI_2; // one quarter rotation per
second
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
27
Drawing with OpenGL ES and GLKit
A GLKit View Controller Animates OpenGL ES Content
// Set up transform matrices for the rotating planet
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(_rotation, 0.0f, 1.0f,
0.0f);
_normalMatrix =
GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
_modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix,
modelViewMatrix);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
// Clear the framebuffer
glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Set shader uniforms to values calculated in -update
glUseProgram(_diffuseShading);
glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0,
_modelViewProjectionMatrix.m);
glUniformMatrix3fv(_uniformNormalMatrix, 1, 0, _normalMatrix.m);
// Draw using previously configured texture and vertex array
glBindTexture(GL_TEXTURE_2D, _planetTexture);
glBindVertexArrayOES(_planetMesh);
glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT, 0);
}
@end
In this example, an instance of the PlanetViewController class (a custom GLKViewController subclass)
is loaded from a storyboard, along with a standard GLKView instance and its drawable properties. The
viewDidLoad method creates an OpenGL ES context and provides it to the view, and also sets the frame rate
for the animation loop.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
28
Drawing with OpenGL ES and GLKit
Using GLKit to Develop Your Renderer
The view controller is automatically the delegate of its view, so it implements both the update and display
phases of the animation loop. In the update method, it calculates the transformation matrices needed to
display a rotating planet. In the glkView:drawInRect: method, it provides those matrices to a shader
program and submits drawing commands to render the planet geometry.
Using GLKit to Develop Your Renderer
In addition to view and view controller infrastructure, the GLKit framework provides several other features to
ease OpenGL ES development on iOS.
Handling Vector and Matrix Math
OpenGL ES 2.0 and later doesn’t provide built-in functions for creating or specifying transformation matrices.
Instead, programmable shaders provide vertex transformation, and you specify shader inputs using generic
uniform variables. The GLKit framework includes a comprehensive library of vector and matrix types and
functions, optimized for high performance on iOS hardware. (See GLKit Framework Reference .)
Migrating from the OpenGL ES 1.1 Fixed-Function Pipeline
OpenGL ES 2.0 and later removes all functionality associated with the OpenGL ES 1.1 fixed-function graphics
pipeline. The GLKBaseEffect class provides an Objective-C analog to the transformation, lighting and shading
stages of the OpenGL ES 1.1 pipeline, and the GLKSkyboxEffect and GLKReflectionMapEffect classes
add support for common visual effects. See the reference documentation for these classes for details.
Loading Texture Data
The GLKTextureLoader class provides a simple way to load texture data from any image format supported
by iOS into an OpenGL ES context, synchronously or asynchronously. (See “Use the GLKit Framework to Load
Texture Data” (page 80).)
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
29
Drawing to Other Rendering Destinations
Framebuffer objects are the destination for rendering commands. When you create a framebuffer object, you
have precise control over its storage for color, depth, and stencil data. You provide this storage by attaching
images to the framebuffer, as shown in Figure 4-1. The most common image attachment is a renderbuffer
object. You can also attach an OpenGL ES texture to the color attachment point of a framebuffer, which means
that any drawing commands are rendered into the texture. Later, the texture can act as an input to future
rendering commands. You can also create multiple framebuffer objects in an single rendering context. You
might do this so that you can share the same rendering pipeline and OpenGL ES resources between multiple
framebuffers.
Figure 4-1
Framebuffer with color and depth renderbuffers
All of these approaches require manually creating framebuffer and renderbuffer objects to store the rendering
results from your OpenGL ES context, as well as writing additional code to present their contents to the screen
and (if needed) run an animation loop.
Creating a Framebuffer Object
Depending on what task your app intends to perform, your app configures different objects to attach to the
framebuffer object. In most cases, the difference in configuring the framebuffer is in what object is attached
to the framebuffer object’s color attachment point:
●
To use the framebuffer for offscreen image processing, attach a renderbuffer. See “Creating Offscreen
Framebuffer Objects” (page 31).
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
30
Drawing to Other Rendering Destinations
Creating a Framebuffer Object
●
To use the framebuffer image as an input to a later rendering step, attach a texture. See “Using Framebuffer
Objects to Render to a Texture” (page 32).
●
To use the framebuffer in a Core Animation layer composition, use a special Core Animation–aware
renderbuffer. See “Rendering to a Core Animation Layer” (page 33).
Creating Offscreen Framebuffer Objects
A framebuffer intended for offscreen rendering allocates all of its attachments as OpenGL ES renderbuffers.
The following code allocates a framebuffer object with color and depth attachments.
1.
Create the framebuffer and bind it.
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
2.
Create a color renderbuffer, allocate storage for it, and attach it to the framebuffer’s color attachment
point.
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, colorRenderbuffer);
3.
Create a depth or depth/stencil renderbuffer, allocate storage for it, and attach it to the framebuffer’s
depth attachment point.
GLuint depthRenderbuffer;
glGenRenderbuffers(1, &depthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width,
height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, depthRenderbuffer);
4.
Test the framebuffer for completeness. This test only needs to be performed when the framebuffer’s
configuration changes.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
31
Drawing to Other Rendering Destinations
Creating a Framebuffer Object
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
if(status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"failed to make complete framebuffer object %x", status);
}
After drawing to an offscreen renderbuffer, you can return its contents to the CPU for further processing using
the glReadPixels function.
Using Framebuffer Objects to Render to a Texture
The code to create this framebuffer is almost identical to the offscreen example, but now a texture is allocated
and attached to the color attachment point.
1.
Create the framebuffer object (using the same procedure as in “Creating Offscreen Framebuffer
Objects” (page 31)).
2.
Create the destination texture, and attach it to the framebuffer’s color attachment point.
// create the texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,
GL_UNSIGNED_BYTE, NULL);
width, height, 0, GL_RGBA,
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
texture, 0);
3.
Allocate and attach a depth buffer (as before).
4.
Test the framebuffer for completeness (as before).
Although this example assumes you are rendering to a color texture, other options are possible. For example,
using the OES_depth_texture extension, you can attach a texture to the depth attachment point to store depth
information from the scene into a texture. You might use this depth information to calculate shadows in the
final rendered scene.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
32
Drawing to Other Rendering Destinations
Creating a Framebuffer Object
Rendering to a Core Animation Layer
Core Animation is the central infrastructure for graphics rendering and animation on iOS. You can compose
your app’s user interface or other visual displays using layers that host content rendered using different iOS
subsystems, such as UIKit, Quartz 2D, and OpenGL ES. OpenGL ES connects to Core Animation through the
CAEAGLLayer class, a special type of Core Animation layer whose contents come from an OpenGL ES
renderbuffer. Core Animation composites the renderbuffer’s contents with other layers and displays the resulting
image on screen.
Figure 4-2
Core Animation shares the renderbuffer with OpenGL ES
The CAEAGLLayer provides this support to OpenGL ES by providing two key pieces of functionality. First, it
allocates shared storage for a renderbuffer. Second, it presents the renderbuffer to Core Animation, replacing
the layer’s previous contents with data from the renderbuffer. An advantage of this model is that the contents
of the Core Animation layer do not need to be drawn in every frame, only when the rendered image changes.
Note: The GLKView class automates the steps below, so you should use it when you want to draw
with OpenGL ES in the content layer of a view.
To use a Core Animation layer for OpenGL ES rendering:
1.
Create a CAEAGLLayer object and configure its properties.
For optimal performance, set the value of the layer’s opaque property to YES. See “Be Aware of Core
Animation Compositing Performance” (page 62).
Optionally, configure the surface properties of the rendering surface by assigning a new dictionary of
values to the drawableProperties property of the CAEAGLLayer object. You can specify the pixel
format for the renderbuffer and specify whether the renderbuffer’s contents are discarded after they are
sent to Core Animation. For a list of the permitted keys, see EAGLDrawable Protocol Reference .
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
33
Drawing to Other Rendering Destinations
Creating a Framebuffer Object
2.
Allocate an OpenGL ES context and make it the current context. See “Configuring OpenGL ES
Contexts” (page 17).
3.
Create the framebuffer object (as in “Creating Offscreen Framebuffer Objects” (page 31) above).
4.
Create a color renderbuffer, allocating its storage by calling the context’s
renderbufferStorage:fromDrawable: method and passing the layer object as the parameter. The
width, height and pixel format are taken from the layer and used to allocate storage for the renderbuffer.
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, colorRenderbuffer);
Note: When the Core Animation layer’s bounds or properties change, your app should reallocate
the renderbuffer’s storage. If you do not reallocate the renderbuffers, the renderbuffer size won’t
match the size of the layer; in this case, Core Animation may scale the image’s contents to fit in
the layer.
5.
Retrieve the height and width of the color renderbuffer.
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH,
&width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT,
&height);
In earlier examples, the width and height of the renderbuffers were explicitly provided to allocate storage
for the buffer. Here, the code retrieves the width and height from the color renderbuffer after its storage
is allocated. Your app does this because the actual dimensions of the color renderbuffer are calculated
based on the layer’s bounds and scale factor. Other renderbuffers attached to the framebuffer must have
the same dimensions. In addition to using the height and width to allocate the depth buffer, use them to
assign the OpenGL ES viewport and to help determine the level of detail required in your app’s textures
and models. See “Supporting High-Resolution Displays” (page 43).
6.
Allocate and attach a depth buffer (as before).
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
34
Drawing to Other Rendering Destinations
Drawing to a Framebuffer Object
7.
Test the framebuffer for completeness (as before).
8.
Add the CAEAGLLayer object to your Core Animation layer hierarchy by passing it to the addSublayer:
method of a visible layer.
Drawing to a Framebuffer Object
Now that you have a framebuffer object, you need to fill it. This section describes the steps required to render
new frames and present them to the user. Rendering to a texture or offscreen framebuffer acts similarly, differing
only in how your app uses the final frame.
Rendering on Demand or with an Animation Loop
You must choose when to draw your OpenGL ES content when rendering to a Core Animation layer, just as
when drawing with GLKit views and view controllers. If rendering to an offscreen framebuffer or texture, draw
whenever is appropriate to the situations where you use those types of framebuffers.
For on-demand drawing, implement your own method to draw into and present your renderbuffer, and call
it whenever you want to display new content.
To draw with an animation loop, use a CADisplayLink object. A display link is a kind of timer provided by
Core Animation that lets you synchronize drawing to the refresh rate of a screen. Listing 4-1 (page 35) shows
how you can retrieve the screen showing a view, use that screen to create a new display link object and add
the display link object to the run loop.
Note: The GLKViewController class automates the usage of CADisplayLink objects for animating
GLKView content. Use the CADisplayLink class directly only if you need behavior beyond what
the GLKit framework provides.
Listing 4-1
Creating and starting a display link
displayLink = [myView.window.screen displayLinkWithTarget:self
selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
Inside your implementation of the drawFrame method, read the display link’s timestamp property to get the
timestamp for the next frame to be rendered. It can use that value to calculate the positions of objects in the
next frame.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
35
Drawing to Other Rendering Destinations
Drawing to a Framebuffer Object
Normally, the display link object is fired every time the screen refreshes; that value is usually 60 Hz, but may
vary on different devices. Most apps do not need to update the screen 60 times per second. You can set the
display link’s frameInterval property to the number of actual frames that go by before your method is
called. For example, if the frame interval was set to 3, your app is called every third frame, or roughly 20 frames
per second.
Important: For best results, choose a frame rate your app can consistently achieve. A smooth, consistent
frame rate produces a more pleasant user experience than a frame rate that varies erratically.
Rendering a Frame
Figure 4-3 (page 36) shows the steps an OpenGL ES app should take on iOS to render and present a frame.
These steps include many hints to improve performance in your app.
Figure 4-3
iOS OpenGL Rendering Steps
Clear Buffers
At the start of every frame, erase the contents of all framebuffer attachments whose contents from a previous
frames are not needed to draw the next frame. Call the glClear function, passing in a bit mask with all of the
buffers to clear, as shown in Listing 4-2.
Listing 4-2
Clear framebuffer attachments
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
Using glClear hints to OpenGL ES that the existing contents of a renderbuffer or texture can be discarded,
avoiding costly operations to load the previous contents into memory.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
36
Drawing to Other Rendering Destinations
Drawing to a Framebuffer Object
Prepare OpenGL ES Objects
This step and the next step is the heart of your app, where you decide what you want to display to the user.
In this step, you prepare all of the OpenGL ES objects — vertex buffer objects, textures and so on — that are
needed to render the frame.
Execute Drawing Commands
This step takes the objects you prepared in the previous step and submits drawing commands to use them.
Designing this portion of your rendering code to run efficiently is covered in detail in “OpenGL ES Design
Guidelines” (page 46). For now, the most important performance optimization to note is that your app runs
faster if it only modifies OpenGL ES objects at the start of rendering a new frame. Although your app can
alternate between modifying objects and submitting drawing commands (as shown by the dotted line), it runs
faster if it only performs each step once.
Resolve Multisampling
If your app uses multisampling to improve image quality, your app must resolve the pixels before they are
presented to the user. Multisampling is covered in detail in “Using Multisampling to Improve Image
Quality” (page 38).
Discard Unneeded Renderbuffers
A discard operation is a performance hint that tells OpenGL ES that the contents of one or more renderbuffers
are no longer needed. By hinting to OpenGL ES that you do not need the contents of a renderbuffer, the data
in the buffers can be discarded and expensive tasks to keep the contents of those buffers updated can be
avoided.
At this stage in the rendering loop, your app has submitted all of its drawing commands for the frame. While
your app needs the color renderbuffer to display to the screen, it probably does not need the depth buffer’s
contents. Listing 4-3 discards the contents of the depth buffer.
Listing 4-3
Discarding the depth framebuffer
const GLenum discards[]
= {GL_DEPTH_ATTACHMENT};
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glDiscardFramebufferEXT(GL_FRAMEBUFFER,1,discards);
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
37
Drawing to Other Rendering Destinations
Using Multisampling to Improve Image Quality
Note: The glDiscardFramebufferEXT function is provided by the EXT_discard_framebuffer
extension for OpenGL ES 1.1 and 2.0. In a OpenGL ES 3.0 context, use the
glInvalidateFramebuffer function instead.
Present the Results to Core Animation
At this step, the color renderbuffer holds the completed frame, so all you need to do is present it to the user.
Listing 4-4 (page 38) binds the renderbuffer to the context and presents it. This causes the completed frame
to be handed to Core Animation.
Listing 4-4
Presenting the finished frame
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];
By default, you must assume that the contents of the renderbuffer are discarded after your app presents the
renderbuffer. This means that every time your app presents a frame, it must completely re-create the frame’s
contents when it renders a new frame. The code above always erases the color buffer for this reason.
If your app wants to preserve the contents of the color renderbuffer between frames, add the
kEAGLDrawablePropertyRetainedBacking key to the dictionary stored in the drawableProperties
property of the CAEAGLLayer object, and remove the GL_COLOR_BUFFER_BIT constant from the earlier
glClear function call. Retained backing may require iOS to allocate additional memory to preserve the buffer’s
contents, which may reduce your app’s performance.
Using Multisampling to Improve Image Quality
Multisampling is a form of antialiasing that smooths jagged edges and improves image quality in most 3D
apps. OpenGL ES 3.0 includes multisampling as part of the core specification, and iOS provides it in OpenGL ES
1.1 and 2.0 through the APPLE_framebuffer_multisample extension. Multisampling uses more memory and
fragment processing time to render the image, but it may improve image quality at a lower performance cost
than using other approaches.
Figure 4-4 shows how multisampling works in concept. Instead of creating one framebuffer object, your app
now creates two. The first framebuffer object is the multisampling buffer, containing all attachments necessary
to render your content (typically color and depth buffers). The second framebuffer object is the resolve buffer;
it contains only the attachments necessary to display a rendered image to the user (typically a color renderbuffer,
but possibly a texture), allocated exactly as you did before. The multisample renderbuffers are allocated using
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
38
Drawing to Other Rendering Destinations
Using Multisampling to Improve Image Quality
the same dimensions as the resolve framebuffer, but each includes an additional parameter that specifies the
number of samples to store for each pixel. Your app performs all of its rendering to the multisampling buffer
and then generates the final antialiased image by resolving those samples into the resolve buffer.
Figure 4-4
How multisampling works
Listing 4-5 shows the code to create the multisampling buffer. This code uses the width and height of the
previously created buffer. It calls the glRenderbufferStorageMultisampleAPPLE function to create
multisampled storage for the renderbuffer.
Listing 4-5
Creating the multisample buffer
glGenFramebuffers(1, &sampleFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
glGenRenderbuffers(1, &sampleColorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleColorRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_RGBA8_OES, width,
height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
sampleColorRenderbuffer);
glGenRenderbuffers(1, &sampleDepthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleDepthRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16,
width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
sampleDepthRenderbuffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
NSLog(@"Failed to make complete framebuffer object %x",
glCheckFramebufferStatus(GL_FRAMEBUFFER));
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
39
Drawing to Other Rendering Destinations
Using Multisampling to Improve Image Quality
Here are the steps to modify your rendering code to support multisampling:
1.
During the Clear Buffers step, you clear the multisampling framebuffer’s contents.
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
glViewport(0, 0, framebufferWidth, framebufferHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2.
After submitting your drawing commands, you resolve the contents from the multisampling buffer into
the resolve buffer. The samples stored for each pixel are combined into a single sample in the resolve
buffer.
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
glResolveMultisampleFramebufferAPPLE();
3.
In the Discard step, you can discard both renderbuffers attached to the multisample framebuffer. This is
because the contents you plan to present are stored in the resolve framebuffer.
const GLenum discards[]
= {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
4.
In the Present Results step, you present the color renderbuffer attached to the resolve framebuffer.
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];
Multisampling is not free; additional memory is required to store the additional samples, and resolving the
samples into the resolve framebuffer takes time. If you add multisampling to your app, always test your app’s
performance to ensure that it remains acceptable.
Note: The above code assumes an OpenGL ES 1.1 or 2.0 context. Multisampling is part of the core
OpenGL ES 3.0 API, but the functions are different. See the specification for details.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
40
Multitasking, High Resolution, and Other iOS
Features
Many aspects of working with OpenGL ES are platform neutral, but some details of working with OpenGL ES
on iOS bear special consideration. In particular, an iOS app using OpenGL ES must handle multitasking correctly
or risk being terminated when it moves to the background. You should also consider display resolution and
other device features when developing OpenGL ES content for iOS devices.
Implementing a Multitasking-Aware OpenGL ES App
Your app can continue to run when a user switches to another app. For an overall discussion of multitasking
on iOS, see “App States and Multitasking”.
An OpenGL ES app must perform additional work when it is moved into the background. If an app handles
these tasks improperly, it may be terminated by iOS instead. Also, an app may want to free OpenGL ES resources
so that those resources are made available to the foreground app.
Background Apps May Not Execute Commands on the Graphics Hardware
An OpenGL ES app is terminated if it attempts to execute OpenGL ES commands on the graphics hardware.
iOS prevents background apps from accessing the graphics processor so that the frontmost app is always able
to present a great experience to the user. Your app can be terminated not only if it makes OpenGL ES calls
while in the background but also if previously submitted commands are flushed to the GPU while in the
background. Your app must ensure that all previously submitted commands have finished executing before
moving into the background.
If you use a GLKit view and view controller, and only submit OpenGL ES commands during your drawing
method, your app automatically behaves correctly when it moves to the background. The GLKViewController
class, by default, pauses its animation timer when your app becomes inactive, ensuring that your drawing
method is not called.
If you do not use GLKit views or view controllers or if you submit OpenGL ES commands outside a GLKView
drawing method, you must take the following steps to ensure that your app is not terminated in the background:
1.
In your app delegate’s applicationWillResignActive: method, your app should stop its animation
timer (if any), place itself into a known good state, and then call the glFinish function.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
41
Multitasking, High Resolution, and Other iOS Features
Implementing a Multitasking-Aware OpenGL ES App
2.
In your app delegate’s applicationDidEnterBackground: method, your app may want to delete
some of its OpenGL ES objects to make memory and resources available to the foreground app. Call the
glFinish function to ensure that the resources are removed immediately.
3.
After your app exits its applicationDidEnterBackground: method, it must not make any new
OpenGL ES calls. If it makes an OpenGL ES call, it is terminated by iOS.
4.
In your app’s applicationWillEnterForeground: method, re-create any objects and restart your
animation timer.
To summarize, your app needs to call the glFinish function to ensure that all previously submitted commands
are drained from the command buffer and are executed by OpenGL ES. After it moves into the background,
you must avoid all use of OpenGL ES until it moves back into the foreground.
Delete Easily Re-Created Resources Before Moving to the Background
Your app is never required to free up OpenGL ES objects when it moves into the background. Usually, your
app should avoid disposing of its content. Consider two scenarios:
●
A user is playing your game and exits it briefly to check their calendar. When the player returns to your
game, the game’s resources are still in memory, and the game can resume immediately.
●
Your OpenGL ES app is in the background when the user launches another OpenGL ES app. If that app
needs more memory than is available on the device, the system silently and automatically terminates your
app without requiring it to perform any additional work.
Your goal should be to design your app to be a good citizen: This means keeping the time it takes to move to
the foreground as short as possible while also reducing its memory footprint while it is in the background.
Here’s how you should handle the two scenarios:
●
Your app should keep textures, models and other assets in memory; resources that take a long time to
re-create should never be disposed of when your app moves into the background.
●
Your app should dispose of objects that can be quickly and easily re-created. Look for objects that consume
large amounts of memory.
Easy targets are the framebuffers your app allocates to hold rendering results. When your app is in the
background, it is not visible to the user and may not render any new content using OpenGL ES. That means
the memory consumed by your app’s framebuffers is allocated, but is not useful. Also, the contents of the
framebuffers are transitory ; most app re-create the contents of the framebuffer every time they render a new
frame. This makes renderbuffers a memory-intensive resource that can be easily re-created, becoming a good
candidate for an object that can be disposed of when moving into the background.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
42
Multitasking, High Resolution, and Other iOS Features
Supporting High-Resolution Displays
If you use a GLKit view and view controller, the GLKViewController class automatically disposes of its
associated view’s framebuffers when your app moves into the background. If you manually create framebuffers
for other uses, you should dispose of them when your app moves to the background. In either case, you should
also consider what other transitory resources your app can dispose of at that time.
Supporting High-Resolution Displays
By default, the value of a GLKit view’s contentScaleFactor property matches the scale of the screen that
contains it, so its associated framebuffer is configured for rendering at the full resolution of the display. For
more information on how high-resolution displays are supported in UIKit, see “Supporting High-Resolution
Screens In Views”.
If you present OpenGL ES content using a Core Animation layer, its scale factor is set to 1.0 by default. To
draw at the full resolution of a Retina display, you should change the scale factor of the CAEAGLLayer object
to match the screen’s scale factor.
When supporting devices with high resolution displays, you should adjust the model and texture assets of
your app accordingly. When running on a high-resolution device, you might want to choose more detailed
models and textures to render a better image. Conversely, on a standard-resolution device, you can use smaller
models and textures.
Important: Many OpenGL ES API calls express dimensions in screen pixels. If you use a scale factor greater
than 1.0, you should adjust dimensions accordingly when using the glScissor, glBlitFramebuffer,
glLineWidth, or glPointSize functions or the gl_PointSize shader variable.
An important factor when determining how to support high-resolution displays is performance. The doubling
of scale factor on a Retina display quadruples the number of pixels, causing the GPU to process four times as
many fragments. If your app performs many per-fragment calculations, the increase in pixels may reduce the
frame rate. If you find that your app runs significantly slower at a higher scale factor, consider one of the
following options:
●
Optimize your fragment shader’s performance using the performance-tuning guidelines found in this
document.
●
Implement a simpler algorithm in your fragment shader. By doing so, you are reducing the quality of
individual pixels to render the overall image at a higher resolution.
●
Use a fractional scale factor between 1.0 and and the screen’s scale factor. A scale factor of 1.5 provides
better quality than a scale factor of 1.0 but needs to fill fewer pixels than an image scaled to 2.0.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
43
Multitasking, High Resolution, and Other iOS Features
Supporting Multiple Interface Orientations
●
Use lower-precision formats for your GLKView object’s drawableColorFormat and
drawableDepthFormat properties. By doing this, you reduce the memory bandwidth required to operate
on the underlying renderbuffers.
●
Use a lower scale factor and enable multisampling. An added advantage is that multisampling also provides
higher quality on devices that do not support high-resolution displays.
To enable multisampling for a GLKView object, change the value of its drawableMultisample property.
If you are not rendering to a GLKit view, you must manually set up multisampling buffers and resolve them
before presenting a final image (see “Using Multisampling to Improve Image Quality” (page 38)).
Multisampling is not free; additional memory is required to store the additional samples, and resolving
the samples into the resolve framebuffer takes time. If you add multisampling to your app, always test
your app’s performance to ensure that it remains acceptable.
Supporting Multiple Interface Orientations
Like any app, an OpenGL ES app should support the user interface orientations appropriate to its content. You
declare the supported interface orientations for your app in its information property list, or for the view controller
hosting your OpenGL ES content using its supportedInterfaceOrientations method. (See View Controller
Programming Guide for iOS for details.)
By default, the GLKViewController and GLKView classes handle orientation changes automatically: When
the user rotates the device to a supported orientation, the system animates the orientation change and changes
the size of the view controller’s view. When its size changes, a GLKView object adjusts the size of its framebuffer
and viewport accordingly. If you need to respond to this change, implement the viewWillLayoutSubviews
or viewDidLayoutSubviews method in your GLKViewController subclass, or implement the
layoutSubviews method if you’re using a custom GLKView subclass.
If you draw OpenGL ES content using a Core Animation layer, your app should still include a view controller
to manage user interface orientation.
Presenting OpenGL ES Content on External Displays
An iOS device can be attached to an external display. The resolution of an external display and its content scale
factor may differ from the resolution and scale factor of the main screen; your code that renders a frame should
adjust to match.
The procedure for drawing on an external display is almost identical to that running on the main screen.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
44
Multitasking, High Resolution, and Other iOS Features
Presenting OpenGL ES Content on External Displays
1.
Create a window on the external display by following the steps in Multiple Display Programming Guide
for iOS .
2.
Add to the window the appropriate view or view controller objects for your rendering strategy.
●
If rendering with GLKit, set up instances of GLKViewController and GLKView (or your custom
subclasses) and add them to the window using its rootViewController property.
●
If rendering to a Core Animation layer, add the view containing your layer as a subview of the window.
To use an animation loop for rendering, create a display link object optimized for the external display
by retrieving the screen property of the window and calling its
displayLinkWithTarget:selector: method.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
45
OpenGL ES Design Guidelines
OpenGL ES performs many complex operations on your behalf—transformations, lighting, clipping, texturing,
environmental effects, and so on—on large data sets. The size of your data and the complexity of the calculations
performed can impact performance, making your stellar 3D graphics shine less brightly than you'd like. Whether
your app is a game using OpenGL ES to provide immersive real-time images to the user or an image processing
app more concerned with image quality, use the information in this chapter to help you design your graphics
engine. This chapter introduces key concepts that later chapters expand on.
How to Visualize OpenGL ES
There are a few ways you can visualize OpenGL ES, and each provides a slightly different context in which to
design and observe your app. The most common way to visualize OpenGL ES is as a graphics pipeline such as
the one shown in Figure 6-1. Your app configures the graphics pipeline, and then executes one or more drawing
commands. The drawing commands send vertex data down the pipeline, where it is processed, assembled
into primitives, and rasterized into fragments. Each fragment calculates color and depth values which are then
merged into the framebuffer. Using the pipeline as a mental model is essential for identifying exactly what
work your app performs to generate a new frame. In a OpenGL ES 2.0 or 3.0 app, your design consists of writing
customized shaders to handle the vertex and fragment stages of the pipeline. In an OpenGL ES 1.1 app, you
modify the state machine that drives the fixed-function pipeline to perform the desired calculations.
Another benefit of the pipeline model is that individual stages can calculate their results independently and
simultaneously. This is a key point. Your app might prepare new primitives while separate portions of the
graphics hardware perform vertex and fragment calculations on previously submitted geometry. If any pipeline
stage performs too much work or performs too slowly, other pipeline stages sit idle until the slowest stage
completes its work. Your design needs to balance the work performed by each pipeline stage by matching
calculations to the capabilities of the graphics hardware on the device.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
46
OpenGL ES Design Guidelines
How to Visualize OpenGL ES
Important: When you tune your app’s performance, the first step is usually to determine which stage it is
bottlenecked in, and why.
Figure 6-1
OpenGL ES graphics pipeline
Another way to visualize OpenGL ES is as a client-server architecture, as shown in Figure 6-2. OpenGL ES state
changes, texture and vertex data, and rendering commands all have to travel from the app to the OpenGL ES
client. The client transforms these data into a format that the graphics hardware understands, and forwards
them to the GPU. Not only do these transformations add overhead, but the process of transferring the data to
the graphics hardware takes time.
To achieve great performance, an app must reduce the frequency of calls it makes to OpenGL ES, minimize
the transformation overhead, and carefully manage the flow of data between itself and OpenGL ES.
Figure 6-2
OpenGL client-server architecture
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
47
OpenGL ES Design Guidelines
Designing a High-Performance OpenGL ES App
Designing a High-Performance OpenGL ES App
To summarize, a well-designed OpenGL ES app needs to:
●
Exploit parallelism in the OpenGL ES pipeline.
●
Manage data flow between the app and the graphics hardware.
Figure 6-3 suggests a process flow for an app that uses OpenGL ES to perform animation to the display.
Figure 6-3
App model for managing resources
When the app launches, the first thing it does is initialize resources that it does not intend to change over the
lifetime of the app. Ideally, the app encapsulates those resources into OpenGL ES objects. The goal is to create
any object that can remain unchanged for the runtime of the app (or even a portion of the app’s lifetime, such
as the duration of a level in a game), trading increased initialization time for better rendering performance.
Complex commands or state changes should be replaced with OpenGL ES objects that can be used with a
single function call. For example, configuring the fixed-function pipeline can take dozens of function calls.
Instead, compile a graphics shader at initialization time, and switch to it at runtime with a single function call.
OpenGL ES objects that are expensive to create or modify should almost always be created as static objects.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
48
OpenGL ES Design Guidelines
Designing a High-Performance OpenGL ES App
The rendering loop processes all of the items you intend to render to the OpenGL ES context, then presents
the results to the display. In an animated scene, some data is updated for every frame. In the inner rendering
loop shown in Figure 6-3, the app alternates between updating rendering resources (creating or modifying
OpenGL ES objects in the process) and submitting drawing commands that use those resources. The goal of
this inner loop is to balance the workload so that the CPU and GPU are working in parallel, preventing the app
and OpenGL ES from accessing the same resources simultaneously. On iOS, modifying an OpenGL ES object
can be expensive when the modification is not performed at the start or the end of a frame.
An important goal for this inner loop is to avoid copying data back from OpenGL ES to the app. Copying results
from the GPU to the CPU can be very slow. If the copied data is also used later as part of the process of rendering
the current frame, as shown in the middle rendering loop, your app blocks until all previously submitted
drawing commands are completed.
After the app submits all drawing commands needed in the frame, it presents the results to the screen. A
non-interactive app would copy the final image to app memory for further processing.
Finally, when your app is ready to quit, or when it finishes with a major task, it frees OpenGL ES objects to
make additional resources available, either for itself or for other apps.
To summarize the important characteristics of this design:
●
Create static resources whenever practical.
●
The inner rendering loop alternates between modifying dynamic resources and submitting rendering
commands. Try to avoid modifying dynamic resources except at the beginning or the end of a frame.
●
Avoid reading intermediate rendering results back to your app.
The rest of this chapter provides useful OpenGL ES programming techniques to implement the features of this
rendering loop. Later chapters demonstrate how to apply these general techniques to specific areas of
OpenGL ES programming.
●
“Avoid Synchronizing and Flushing Operations” (page 50)
●
“Avoid Querying OpenGL ES State” (page 51)
●
“Use OpenGL ES to Manage Your Resources” (page 51)
●
“Use Double Buffering to Avoid Resource Conflicts” (page 51)
●
“Be Mindful of OpenGL ES State Variables” (page 53)
●
“Replace State Changes with OpenGL ES Objects” (page 54)
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
49
OpenGL ES Design Guidelines
Avoid Synchronizing and Flushing Operations
Avoid Synchronizing and Flushing Operations
The OpenGL ES specification doesn’t require implementations to execute commands immediately. Often,
commands are queued to a command buffer and executed by the hardware at a later time. Usually, OpenGL ES
waits until the app has queued many commands before sending the commands to the hardware—batch
processing is usually more efficient. However, some OpenGL ES functions must flush the command buffer
immediately. Other functions not only flush the command buffer but also block until previously submitted
commands have completed before returning control over the app. Use flushing and synchronizing commands
only when that behavior is necessary. Excessive use of flushing or synchronizing commands may cause your
app to stall while it waits for the hardware to finish rendering.
These situations require OpenGL ES to submit the command buffer to the hardware for execution.
●
The function glFlush sends the command buffer to the graphics hardware. It blocks until commands
are submitted to the hardware but does not wait for the commands to finish executing.
●
The function glFinish flushes the command buffer and then waits for all previously submitted commands
to finish executing on the graphics hardware.
●
Functions that retrieve framebuffer content (such as glReadPixels) also wait for submitted commands
to complete.
●
The command buffer is full.
Using glFlush Effectively
On some desktop OpenGL implementations, it can be useful to periodically call the glFlush function to
efficiently balance CPU and GPU work, but this is not the case in iOS. The Tile-Based Deferred Rendering
algorithm implemented by iOS graphics hardware depends on buffering all vertex data in a scene at once, so
it can be optimally processed for hidden surface removal. Typically, there are only two situations where an
OpenGL ES app should call the glFlush or glFinish functions.
●
You should flush the command buffer when your app moves to the background, because executing
OpenGL ES commands on the GPU while your app is in the background causes iOS to terminate your app.
(See “Implementing a Multitasking-Aware OpenGL ES App” (page 41).)
●
If your app shares OpenGL ES objects (such as vertex buffers or textures) between multiple contexts, you
should call the glFlush function to synchronize access to these resources. For example, you should call
the glFlush function after loading vertex data in one context to ensure that its contents are ready to be
retrieved by another context. This advice also applies when sharing OpenGL ES objects with other iOS
APIs such as Core Image.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
50
OpenGL ES Design Guidelines
Use OpenGL ES to Manage Your Resources
Avoid Querying OpenGL ES State
Calls to glGet*(), including glGetError(), may require OpenGL ES to execute previous commands before
retrieving any state variables. This synchronization forces the graphics hardware to run lockstep with the CPU,
reducing opportunities for parallelism. To avoid this, maintain your own copy of any state you need to query,
and access it directly, rather than calling OpenGL ES.
When errors occur, OpenGL ES sets an error flag. These and other errors appear in OpenGL ES Frame Debugger
in Xcode or OpenGL ES Analyzer in Instruments. You should use those tools instead of the glGetError
function, which degrades performance if called frequently. Other queries such as
glCheckFramebufferStatus(), glGetProgramInfoLog() and glValidateProgram() are also generally
only useful while developing and debugging. You should omit calls to these functions in Release builds of your
app.
Use OpenGL ES to Manage Your Resources
Many pieces of OpenGL data can be stored directly inside the OpenGL ES rendering context and its associated
sharegroup object. The OpenGL ES implementation is free to transform the data into a format that is optimal
for the graphics hardware. This can significantly improve performance, especially for data that changes
infrequently. Your app can also provide hints to OpenGL ES about how it intends to use the data. An OpenGL ES
implementation can use these hints to process the data more efficiently. For example, static data might be
placed in memory that the graphics processor can readily fetch, or even into dedicated graphics memory.
Use Double Buffering to Avoid Resource Conflicts
Resource conflicts occur when your app and OpenGL ES access an OpenGL ES object at the same time. When
one participant attempts to modify an OpenGL ES object being used by the other, they may block until the
object is no longer in use. Once they begin modifying the object, the other participant may not access the
object until the modifications are complete. Alternatively, OpenGL ES may implicitly duplicate the object so
that both participants can continue to execute commands. Either option is safe, but each can end up as a
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
51
OpenGL ES Design Guidelines
Use Double Buffering to Avoid Resource Conflicts
bottleneck in your app. Figure 6-4 shows this problem. In this example, there is a single texture object, which
both OpenGL ES and your app want to use. When the app attempts to change the texture, it must wait until
previously submitted drawing commands complete—the CPU synchronizes to the GPU.
Figure 6-4
Single-buffered texture data
To solve this problem, your app could perform additional work between changing the object and drawing
with it. But, if your app does not have additional work it can perform, it should explicitly create two identically
sized objects; while one participant reads an object, the other participant modifies the other. Figure 6-5
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
52
OpenGL ES Design Guidelines
Be Mindful of OpenGL ES State Variables
illustrates the double-buffered approach. While the GPU operates on one texture, the CPU modifies the other.
After the initial startup, neither the CPU or GPU sits idle. Although shown for textures, this solution works for
almost any type of OpenGL ES object.
Figure 6-5
Double-buffered texture data
Double buffering is sufficient for most apps, but it requires that both participants finish processing commands
in roughly the same time. To avoid blocking, you can add more buffers; this implements a traditional
producer-consumer model. If the producer finishes before the consumer finishes processing commands, it
takes an idle buffer and continues to process commands. In this situation, the producer idles only if the consumer
falls badly behind.
Double and triple buffering trade off consuming additional memory to prevent the pipeline from stalling. The
additional use of memory may cause pressure on other parts of your app. On an iOS device, memory can be
scarce; your design may need to balance using more memory with other app optimizations.
Be Mindful of OpenGL ES State Variables
The hardware has one current state, which is compiled and cached. Switching state is expensive, so it's best
to design your app to minimize state switches.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
53
OpenGL ES Design Guidelines
Replace State Changes with OpenGL ES Objects
Don't set a state that's already set. Once a feature is enabled, it does not need to be enabled again. Calling an
enable function more than once does nothing except waste time because OpenGL ES does not check the state
of a feature when you call glEnable or glDisable. For instance, if you call glEnable(GL_LIGHTING) more
than once, OpenGL ES does not check to see if the lighting state is already enabled. It simply updates the state
value even if that value is identical to the current value.
You can avoid setting a state more than necessary by using dedicated setup or shutdown routines rather than
putting such calls in a drawing loop. Setup and shutdown routines are also useful for turning on and off features
that achieve a specific visual effect—for example, when drawing a wire-frame outline around a textured
polygon.
If you are drawing 2D images, disable all irrelevant state variables, similar to what's shown in Listing 6-1.
Listing 6-1
Disabling state variables on OpenGL ES 1.1
glDisable(GL_DITHER);
glDisable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
glDisable(GL_FOG);
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
// Disable other state variables as appropriate.
Replace State Changes with OpenGL ES Objects
The “Be Mindful of OpenGL ES State Variables” (page 53) section suggests that reducing the number of state
changes can improve performance. Some OpenGL ES extensions can create objects that collect multiple OpenGL
state changes into an object that can be bound with a single function call. Where such techniques are available,
they are recommended. For example, configuring the fixed-function pipeline requires many function calls to
change the state of the various operators. Not only does this incur overhead for each function called, but the
code is more complex and difficult to manage. Instead, use a shader. A shader, once compiled, can have the
same effect but requires only a single call to glUseProgram.
For another example, vertex array objects store the configuration of multiple vertex attributes into a single
vertex array object. See “Consolidate Vertex Array State Changes Using Vertex Array Objects” (page 77).
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
54
Tuning Your OpenGL ES App
The performance of OpenGL ES apps in iOS differs from that of OpenGL in OS X or other desktop operating
systems. Although powerful computing devices, iOS–based devices do not have the memory or CPU power
that desktop or laptop computers possess. Embedded GPUs are optimized for lower memory and power usage,
using algorithms different from those a typical desktop or laptop GPU might use. Rendering your graphics
data inefficiently can result in a poor frame rate or dramatically reduce the battery life of an iOS-based device.
Later chapters will touch on many techniques to improve your app’s performance; this chapter talks about
overall strategies you may want to follow.
General Performance Recommendations
Use common sense to guide your performance tuning efforts. For example, if your app draws only a few dozen
triangles per frame, changing how it submits vertex data is unlikely to improve its performance. Look for
optimizations that provide the most performance improvement for your effort.
Test Your App with Xcode
Don’t optimize your app until you have tested its performance under a variety of scenarios on a variety of
devices. Use these tools in Xcode and Instruments to look for errors and performance issues while your app
runs:
●
OpenGL ES Debug Gauge. When debugging an OpenGL ES app on a device, the Xcode debug navigator
includes an FPS (frames per second) indicator below the default CPU and Memory debug gauges. Click
this indicator to show a real-time report of your app’s OpenGL ES performance in the editor area. This
debug gauge quickly helps you to determine whether OpenGL ES is the main bottleneck in your app; you
should refer to it often when testing your OpenGL ES code.
●
Instruments (OpenGL ES Analysis). This tool helps you study your app’s usage of OpenGL ES. The
OpenGL ES Analysis tool records the OpenGL ES commands generated by your app and warns you when
your app does not follow the best practices described in this programming guide; it recommends specific
changes you can make to follow the best practices. You can see all the commands used to generate each
frame of animation. Finally, you can selectively disable portions of the graphics pipeline to determine
whether that part of the pipeline is a significant bottleneck in your app.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
55
Tuning Your OpenGL ES App
General Performance Recommendations
The OpenGL ES Analysis tool provides you a great set of tools to manually analyze your app and understand
its inner workings. It does not, however, automatically point you at the location where your app is currently
bottlenecked. For example, even when it offers a suggestion on how to improve your OpenGL ES coding
practices, following that suggestion won’t necessarily improve the performance of your app.
●
Instruments (OpenGL ES Driver). This tool tracks how resources are used by your app. For example, you
can use OpenGL ES Driver to track the number of bytes used to hold texture data and how those numbers
change from frame to frame.
For a more detailed perspective, use these tools in Xcode to look for errors and performance issues when
rendering a specific frame:
●
OpenGL ES Frame Debugger. Xcode can capture the entire sequence of OpenGL ES drawing commands
that produce a displayed frame. To capture a frame while debugging an OpenGL ES app on a device, click
the Capture Frame button on the debug bar or choose Debug > Capture OpenGL ES Frame. You can also
capture a frame as a breakpoint action. After a frame is captured, Xcode reconfigures its user interface for
OpenGL ES frame debugging:
●
The primary editor shows framebuffer and renderbuffer contents.
●
The debug navigator shows the sequence of OpenGL ES commands used to render the frame. Selecting
a command in the navigator changes the framebuffer view to show rendering output only up to that
command. It also highlights any drawing performed by that command.
●
The assistant editor shows OpenGL ES objects. In this editor, you can view the contents of data buffers,
vertex array objects, and textures. You can also view and edit shader source code and see changes
reflected in the framebuffer. On OpenGL ES 3.0–capable devices, you can also see profiling information
for shaders.
●
The debug area shows OpenGL ES object, state variables, errors, performance warnings, and statistics.
Use OpenGL ES Frame Debugger frequently to discover errors and performance issues in your OpenGL ES
drawing code and shaders.
●
OpenGL ES Performance Analyzer. This tool extends OpenGL ES Frame Debugger to analyze common
performance issues. To see a list of performance issues when debugging an OpenGL ES app on a device,
click the OpenGL ES debug gauge in the debug navigator, and then click the Analyze button at the top
of the GPU Report that appears in the editor area. After Xcode captures a frame, the GPU Report expands
to show a list of performance issues. For each issue, you can see a list of OpenGL ES calls involved, their
location in your code and in the frame capture, and specific recommendations for improving performance.
A key advantage of the OpenGL ES Performance Analyzer is that it can automatically direct you immediately
to the critical location in your app that slows OpenGL ES performance the most.
For more information, see Xcode Overview and Instruments User Guide .
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
56
Tuning Your OpenGL ES App
General Performance Recommendations
Use Xcode and Instruments to Test for OpenGL ES Errors
OpenGL ES errors result from your app using the OpenGL ES API incorrectly or requesting operations that the
underlying hardware is not capable of performing. Even if your content renders correctly, these errors may
indicate performance problems. The traditional way to check for OpenGL ES errors is to call the glGetError
function; however, repeatedly calling this function can significantly degrade performance. Instead, you can
use the tools outlined above to test for errors:
●
When profiling your app in Instruments, see the detail pane for OpenGL ES Analyzer instrument to view
any OpenGL ES errors reported while recording.
●
While debugging your app in Xcode, you can capture a frame and use OpenGL ES Frame Debugger to
examine the drawing commands used to produce it, as well as any errors encountered while performing
those commands.
You can also configure Xcode to stop program execution when an OpenGL ES error is encountered. (See Adding
an OpenGL ES Error Breakpoint.)
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
57
Tuning Your OpenGL ES App
General Performance Recommendations
Annotate Your Drawing Code for Informative Debugging and Profiling
You can make debugging and profiling more efficient by organizing your stream of OpenGL ES commands
into logical groups and adding meaningful labels to OpenGL ES objects. These groups and labels appear in
OpenGL ES Frame Debugger in Xcode as shown in Figure 7-1, and in OpenGL ES Analyzer in Instruments. To
add groups and labels, use the EXT_debug_marker and EXT_debug_label extensions.
Figure 7-1
Xcode Frame Debugger before and after adding debug marker groups
When you have a sequence of drawing commands that represent a single meaningful operation—for example,
drawing a game character—you can use a marker to group them for debugging. Call the
glPushGroupMarkerEXT function and provide a meaningful name before the drawing calls to be labeled,
and call the glPopGroupMarkerEXT function afterward. Listing 7-1 uses these functions to group the texture,
program, vertex array, and draw calls for a single element of a scene.
Listing 7-1
Using the EXT_debug_marker extension to annotate drawing commands
glPushGroupMarkerEXT(0, "Draw Spaceship");
glBindTexture(GL_TEXTURE_2D, _spaceshipTexture);
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
58
Tuning Your OpenGL ES App
General Performance Recommendations
glUseProgram(_diffuseShading);
glBindVertexArrayOES(_spaceshipMesh);
glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT, 0);
glPopGroupMarkerEXT();
You can use multiple nested markers to create a hierarchy of meaningful groups in a complex scene. When
you use the GLKView class to draw OpenGL ES content, it automatically creates a “Rendering” group containing
all commands in your drawing method. Any markers you create are nested within this group.
Labels can be used to provide meaningful names for OpenGL ES objects, such as textures, shader programs,
and vertex array objects. Call the glLabelObjectEXT function with the Open GL ES identifier for an object
to give it a name to be shown when debugging and profiling. Listing 7-2 illustrates using this function to label
a vertex array object. If you use the GLKTextureLoader class to load texture data, it automatically labels the
OpenGL ES texture objects it creates with their filenames.
Listing 7-2
Using the EXT_debug_label extension to annotate OpenGL ES objects
glGenVertexArraysOES(1, &_spaceshipMesh);
glBindVertexArrayOES(_spaceshipMesh);
glLabelObjectEXT(GL_VERTEX_ARRAY_OBJECT_EXT, _spaceshipMesh, 0, "Spaceship");
Redraw Scenes Only When the Scene Data Changes
Your app should wait until something in the scene changes before rendering a new frame. Core Animation
caches the last image presented to the user and continues to display it until a new frame is presented.
Even when your data changes, it is not necessary to render frames at the speed the hardware processes
commands. A slower but fixed frame rate often appears smoother to the user than a fast but variable frame
rate. A fixed frame rate of 30 frames per second is sufficient for most animation and helps reduce power
consumption.
Disable Unused OpenGL ES Features
Whether you are using the fixed-function pipeline of OpenGL ES 1.1 or shaders in OpenGL ES 2.0 or later, the
best calculation is one that your app never performs. For example, if a calculation can be pre-calculated and
stored in your model data, you can avoid performing that calculation at runtime.
If your app is written for OpenGL ES 2.0 or later, do not create a single shader with lots of switches and
conditionals that performs every task your app needs to render the scene. Instead, compile multiple shader
programs that each perform a specific, focused task.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
59
Tuning Your OpenGL ES App
Memory Is a Scarce Resource on iOS Devices
If your app uses OpenGL ES 1.1, disable any fixed-function operations that are not necessary to render the
scene. For example, if your app does not require lighting or blending, you should disable those functions.
Similarly, if your app draws only 2D models, it should disable fog and depth testing.
Minimize the Number of Draw Calls
Every time your app submits primitives to be processed by OpenGL ES, the CPU spends time preparing the
commands for the graphics hardware. To reduce this overhead, batch your drawing into fewer calls. For example,
you might merge multiple triangle strips into a single strip, as described in “Use Triangle Strips to Batch Vertex
Data” (page 71).
Consolidating models to use a common set of OpenGL state has other advantages in that it reduces the
overhead of changing OpenGL ES state. See “Be Mindful of OpenGL ES State Variables” (page 53).
For best results, consolidate primitives that are drawn in close spacial proximity. Large, sprawling models are
more difficult for your app to efficiently cull when they are not visible in the frame.
Memory Is a Scarce Resource on iOS Devices
Your iOS app shares main memory with the system and other iOS apps. Memory allocated for OpenGL ES
reduces the amount of memory available for other uses in your app. With that in mind, allocate only the memory
that you need and deallocate it as soon as your app no longer needs it. Here are a few ways you can save
memory:
●
After loading an image into an OpenGL ES texture, free the original image.
●
Allocate a depth buffer only when your app requires it.
●
If your app does not need all of its resources at once, load only a subset of the items. For example, a game
might be divided into levels; each loads a subset of the total resources that fits within a more strict resource
limit.
The virtual memory system in iOS does not use a swap file. When a low-memory condition is detected, instead
of writing volatile pages to disk, the virtual memory frees up nonvolatile memory to give your running app
the memory it needs. Your app should strive to use as little memory as possible and be prepared to dispose
of objects that are not essential to your app. Responding to low-memory conditions is covered in detail in the
iOS App Programming Guide .
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
60
Tuning Your OpenGL ES App
Do Not Sort Rendered Objects Unless Necessary
Do Not Sort Rendered Objects Unless Necessary
●
Do not waste time sorting objects front to back. OpenGL ES on all iOS devices implements a tile-based
deferred rendering model that makes this unnecessary. See “OpenGL ES Hardware Processors” for more
information.
●
Do sort objects by their opacity:
1.
Draw opaque objects first.
2.
Next draw objects that require alpha testing (or in an OpenGL ES 2.0 or 3.0 based app, objects that
require the use of discard in the fragment shader). Note that these operations have a performance
penalty, as described in “Avoid Alpha Test and Discard” (page 61).
3.
Finally, draw alpha-blended objects.
Simplify Your Lighting Models
This advice applies both to fixed-function lighting in OpenGL ES 1.1 and shader-based lighting calculations
you use in your custom shaders in OpenGL ES 2.0 or later.
●
Use the fewest lights possible and the simplest lighting type for your app. Consider using directional lights
instead of spot lighting, which require more calculations. Shaders should perform lighting calculations in
model space; consider using simpler lighting equations in your shaders over more complex lighting
algorithms.
●
Pre-compute your lighting and store the color values in a texture that can be sampled by fragment
processing.
Avoid Alpha Test and Discard
Graphics hardware often performs depth testing early in the graphics pipeline, before calculating the fragment’s
color value. If your app uses an alpha test in OpenGL ES 1.1 or the discard instruction in an OpenGL ES 2.0
or 3.0 fragment shader, some hardware depth-buffer optimizations must be disabled. In particular, this may
require a fragment’s color to be completely calculated only to be discarded because the fragment is not visible.
An alternative to using alpha test or discard to kill pixels is to use alpha blending with alpha set to zero. The
color framebuffer is not modified, but the graphics hardware can still use any Z-buffer optimizations it performs.
This does change the value stored in the depth buffer and so may require back-to-front sorting of the transparent
primitives.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
61
Tuning Your OpenGL ES App
Be Aware of Core Animation Compositing Performance
If you need to use alpha testing or a discard instruction, draw these objects separately in the scene after
processing any primitives that do not require it. Place the discard instruction early in the fragment shader
to avoid performing calculations whose results are unused.
Another option for avoiding performance penalties due to discard operations is to use a “Z-Prepass” rendering
strategy. Render your scene once using a simple fragment shader containing only your discard logic (avoiding
expensive lighting calculations) to fill the depth buffer. Then, render your scene again using the GL_EQUAL
depth test function and your lighting shaders. Though multipass rendering normally incurs a performance
penalty, this approach can yield better performance than a single-pass render that involves a large number of
discard operations.
Be Aware of Core Animation Compositing Performance
Core Animation composites the contents of renderbuffers with any other layers in your view hierarchy, regardless
of whether those layers were drawn with OpenGL ES, Quartz or other graphics libraries. That’s helpful, because
it means that OpenGL ES is a first-class citizen to Core Animation. However, mixing OpenGL ES content with
other content takes time; when used improperly, your app may perform too slowly to reach interactive frame
rates.
For the absolute best performance, your app should rely solely on OpenGL ES to render your content. Size the
view that holds your OpenGL ES content to match the screen, make sure its opaque property is set to YES
(the default for GLKView objects) and that no other views or Core Animation layers are visible.
If you render into a Core Animation layer that is composited on top of other layers, making your CAEAGLLayer
object opaque reduces—but doesn’t eliminate—the performance cost. If your CAEAGLLayer object is blended
on top of layers underneath it in the layer hierarchy, the renderbuffer’s color data must be in a premultiplied
alpha format to be composited correctly by Core Animation. Blending OpenGL ES content on top of other
content has a severe performance penalty.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
62
Concurrency and OpenGL ES
Concurrency is the notion of multiple things happening at the same time. In the context of computers,
concurrency usually refers to executing tasks on more than one processor at the same time. By performing
work in parallel, tasks complete sooner, and apps become more responsive to the user. A well-designed
OpenGL ES app already exhibits a specific form of concurrency—concurrency between app processing on the
CPU and OpenGL ES processing on the GPU. Many of the techniques introduced in “OpenGL ES Design
Guidelines” (page 46) are aimed specifically at creating OpenGL apps that exhibit great CPU-GPU parallelism.
Designing a concurrent app means decomposing the work into subtasks and identifying which tasks can safely
operate in parallel and which tasks must be executed sequentially—that is, which tasks are dependent on
either resources used by other tasks or results returned from those tasks.
Each process in iOS is made up of one or more threads. A thread is a stream of execution that runs code for
the process. Apple offers both traditional threads and a feature called Grand Central Dispatch (GCD). Using
Grand Central Dispatch, you can decompose a task into subtasks without manually managing threads. GCD
allocates threads based on the number of cores available on the device and automatically schedules tasks to
those threads.
At a higher level, Cocoa Touch offers NSOperation and NSOperationQueue to provide an Objective-C
abstraction for creating and scheduling units of work.
This chapter does not attempt to describe these technologies in detail. Before you consider how to add
concurrency to your OpenGL ES app, first read Concurrency Programming Guide . If you plan on managing
threads manually, also read Threading Programming Guide . Regardless of which technique you use, there are
additional restrictions when calling OpenGL ES on multithreaded systems. This chapter helps you understand
when multithreading improves your OpenGL ES app’s performance, the restrictions OpenGL ES places on
multithreaded app, and common design strategies you might use to implement concurrency in an OpenGL ES
app.
Identifying Whether You Can Benefit from Concurrency
Creating a multithreaded app requires significant effort in the design, implementation, and testing of your
app. Threads also add complexity and overhead. Your app may need to copy data so that it can be handed to
a worker thread, or multiple threads may need to synchronize access to the same resources. Before you attempt
to implement concurrency in an OpenGL ES app, first optimize your OpenGL ES code in a single-threaded
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
63
Concurrency and OpenGL ES
OpenGL ES Restricts Each Context to a Single Thread
environment using the techniques described in “OpenGL ES Design Guidelines” (page 46). Focus on achieving
great CPU-GPU parallelism first and then assess whether concurrent programming can provide additional
performance.
A good candidate has either or both of the following characteristics:
●
The app performs many tasks on the CPU that are independent of OpenGL ES rendering. Games, for
example, simulate the game world, calculate artificial intelligence from computer-controlled opponents,
and play sound. You can exploit parallelism in this scenario because many of these tasks are not dependent
on your OpenGL ES drawing code.
●
Profiling your app has shown that your OpenGL ES rendering code spends a lot of time in the CPU. In this
scenario, the GPU is idle because your app is incapable of feeding it commands fast enough. If your
CPU-bound code has already been optimized, you may be able to improve its performance further by
splitting the work into tasks that execute concurrently.
If your app is blocked waiting for the GPU, and has no work it can perform in parallel with its OpenGL ES
drawing, then it is not a good candidate for concurrency. If the CPU and GPU are both idle, then your OpenGL ES
needs are probably simple enough that no further tuning is needed.
OpenGL ES Restricts Each Context to a Single Thread
Each thread in iOS has a single current OpenGL ES rendering context. Every time your app calls an OpenGL ES
function, OpenGL ES implicitly looks up the context associated with the current thread and modifies the state
or objects associated with that context.
OpenGL ES is not reentrant. If you modify the same context from multiple threads simultaneously, the results
are unpredictable. Your app might crash or it might render improperly. If for some reason you decide to set
more than one thread to target the same context, then you must synchronize threads by placing a mutex
around all OpenGL ES calls to the context. OpenGL ES commands that block—such as glFinish—do not
synchronize threads.
GCD and NSOperationQueue objects can execute your tasks on a thread of their choosing. They may create
a thread specifically for that task, or they may reuse an existing thread. But in either case, you cannot guarantee
which thread executes the task. For an OpenGL ES app, that means:
●
Each task must set the context before executing any OpenGL ES commands.
●
Two tasks that access the same context may never execute simultaneously.
●
Each task should clear the thread’s context before exiting.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
64
Concurrency and OpenGL ES
Strategies for Implementing Concurrency in OpenGL ES Apps
Strategies for Implementing Concurrency in OpenGL ES Apps
A concurrent OpenGL ES app should focus on CPU parallelism so that OpenGL ES can provide more work to
the GPU. Here are a few recommended strategies for implementing concurrency in an OpenGL app:
●
Decompose your app into OpenGL ES and non-OpenGL ES tasks that can execute concurrently. Your
OpenGL ES drawing code executes as a single task, so it still executes in a single thread. This strategy
works best when your app has other tasks that require significant CPU processing.
●
If your app spends a lot of CPU time preparing data to send to OpenGL ES, you can divide the work between
tasks that prepare rendering data and tasks that submit rendering commands to OpenGL ES. See “OpenGL ES
Restricts Each Context to a Single Thread” (page 64)
●
If your app has multiple scenes it can render simultaneously or work it can perform in multiple contexts,
it can create multiple tasks, with one OpenGL ES context per task. If the contexts need access to the same
art assets, use a sharegroup to share OpenGL ES objects between the contexts. See “An EAGL Sharegroup
Manages OpenGL ES Objects for the Context” (page 19).
Perform OpenGL ES Computations in a Worker Task
Some app perform lots of calculations on their data before passing the data down to OpenGL ES. For example,
the app might create new geometry or animate existing geometry. Where possible, such calculations should
be performed inside OpenGL ES. This takes advantage of the greater parallelism available inside the GPU, and
reduces the overhead of copying results between your app and OpenGL ES.
The approach described in Figure 6-3 (page 48) alternates between updating OpenGL ES objects and executing
rendering commands that use those objects. OpenGL ES renders on the GPU in parallel with your app’s updates
running on the CPU. If the calculations performed on the CPU take more processing time than those on the
GPU, then the GPU spends more time idle. In this situation, you may be able to take advantage of parallelism
on systems with multiple CPUs. Split your OpenGL ES rendering code into separate calculation and processing
tasks, and run them in parallel. One task produces data that is consumed by the second and submitted to
OpenGL.
For best performance, avoid copying data between tasks. Rather than calculating the data in one task and
copying it into a vertex buffer object in the other, map the vertex buffer object in the setup code and hand
the pointer directly to the worker task.
If you can further decompose the modifications task into subtasks, you may see better benefits. For example,
assume two or more vertex buffer objects, each of which needs to be updated before submitting drawing
commands. Each can be recalculated independently of the others. In this scenario, the modifications to each
buffer becomes an operation, using an NSOperationQueue object to manage the work:
1.
Set the current context.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
65
Concurrency and OpenGL ES
Use Multiple OpenGL ES Contexts
2.
Map the first buffer.
3.
Create an NSOperation object whose task is to fill that buffer.
4.
Queue that operation on the operation queue.
5.
Perform steps 2 through 4 for the other buffers.
6.
Call waitUntilAllOperationsAreFinished on the operation queue.
7.
Unmap the buffers.
8.
Execute rendering commands.
Use Multiple OpenGL ES Contexts
One common approach for using multiple contexts is to have one context that updates OpenGL ES objects
while the other consumes those resources, with each context running on a separate thread. Because each
context runs on a separate thread, its actions are rarely blocked by the other context. To implement this, your
app would create two contexts and two threads; each thread controls one context. Further, any OpenGL ES
objects your app intends to update on the second thread must be double buffered; a consuming thread may
not access an OpenGL ES object while the other thread is modifying it. The process of synchronizing the
changes between the contexts is described in detail in “An EAGL Sharegroup Manages OpenGL ES Objects for
the Context” (page 19).
The GLKTextureLoader class implements this strategy to provide asynchronous loading of texture data. (See
“Use the GLKit Framework to Load Texture Data” (page 80).)
Guidelines for Threading OpenGL ES Apps
Follow these guidelines to ensure successful threading in an app that uses OpenGL ES:
●
Use only one thread per context. OpenGL ES commands for a specific context are not thread safe. Never
have more than one thread accessing a single context simultaneously.
●
When using GCD, use a dedicated serial queue to dispatch commands to OpenGL ES; this can be used to
replace the conventional mutex pattern.
●
Keep track of the current context. When switching threads it is easy to switch contexts inadvertently, which
causes unforeseen effects on the execution of graphic commands. You must set a current context when
switching to a newly created thread and clear the current context before leaving the thread.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
66
Best Practices for Working with Vertex Data
To render a frame using OpenGL ES your app configures the graphics pipeline and submits graphics primitives
to be drawn. In some apps, all primitives are drawn using the same pipeline configuration; other apps may
render different elements of the frame using different techniques. But no matter which primitives you use in
your app or how the pipeline is configured, your app provides vertices to OpenGL ES. This chapter provides a
refresher on vertex data and follows it with targeted advice for how to efficiently process vertex data.
A vertex consists of one or more attributes, such as the position, the color, the normal, or texture coordinates.
An OpenGL ES 2.0 or 3.0 app is free to define its own attributes; each attribute in the vertex data corresponds
to an attribute variable that acts as an input to the vertex shader. An OpenGL 1.1 app uses attributes defined
by the fixed-function pipeline.
You define an attribute as a vector consisting of one to four components. All components in the attribute
share a common data type. For example, a color might be defined as four GLubyte components (red, green,
blue, alpha). When an attribute is loaded into a shader variable, any components that are not provided in the
app data are filled in with default values by OpenGL ES. The last component is filled with 1, and other unspecified
components are filled with 0, as illustrated in Figure 9-1.
Figure 9-1
Conversion of attribute data to shader variables
Your app may configure an attribute to be a constant , which means the same values are used for all vertices
submitted as part of a draw command, or an array , which means that each vertex a value for that attribute.
When your app calls a function in OpenGL ES to draw a set of vertices, the vertex data is copied from your app
to the graphics hardware. The graphics hardware than acts on the vertex data, processing each vertex in the
shader, assembling primitives and rasterizing them out into the framebuffer. One advantage of OpenGL ES is
that it standardizes on a single set of functions to submit vertex data to OpenGL ES, removing older and less
efficient mechanisms that were provided by OpenGL.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
67
Best Practices for Working with Vertex Data
Simplify Your Models
Apps that must submit a large number of primitives to render a frame need to carefully manage their vertex
data and how they provide it to OpenGL ES. The practices described in this chapter can be summarized in a
few basic principles:
●
Reduce the size of your vertex data.
●
Reduce the pre-processing that must occur before OpenGL ES can transfer the vertex data to the graphics
hardware.
●
Reduce the time spent copying vertex data to the graphics hardware.
●
Reduce computations performed for each vertex.
Simplify Your Models
The graphics hardware of iOS-based devices is very powerful, but the images it displays are often very small.
You don’t need extremely complex models to present compelling graphics on iOS. Reducing the number of
vertices used to draw a model directly reduces the size of the vertex data and the calculations performed on
your vertex data.
You can reduce the complexity of a model by using some of the following techniques:
●
Provide multiple versions of your model at different levels of detail, and choose an appropriate model at
runtime based on the distance of the object from the camera and the dimensions of the display.
●
Use textures to eliminate the need for some vertex information. For example, a bump map can be used
to add detail to a model without adding more vertex data.
●
Some models add vertices to improve lighting details or rendering quality. This is usually done when
values are calculated for each vertex and interpolated across the triangle during the rasterization stage.
For example, if you directed a spotlight at the center of a triangle, its effect might go unnoticed because
the brightest part of the spotlight is not directed at a vertex. By adding vertices, you provide additional
interpolant points, at the cost of increasing the size of your vertex data and the calculations performed
on the model. Instead of adding additional vertices, consider moving calculations into the fragment stage
of the pipeline instead:
●
If your app uses OpenGL ES 2.0 or later, then your app performs the calculation in the vertex shader
and assigns it to a varying variable. The varying value is interpolated by the graphics hardware and
passed to the fragment shader as an input. Instead, assign the calculation’s inputs to varying variables
and perform the calculation in the fragment shader. Doing this changes the cost of performing that
calculation from a per-vertex cost to a per-fragment cost, reduces pressure on the vertex stage and
more pressure on the fragment stage of the pipeline. Do this when your app is blocked on vertex
processing, the calculation is inexpensive and the vertex count can be significantly reduced by the
change.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
68
Best Practices for Working with Vertex Data
Avoid Storing Constants in Attribute Arrays
●
If your app uses OpenGL ES 1.1, you can perform per-fragment lighting using DOT3 lighting. You do
this by adding a bump map texture to hold normal information and applying the bump map using
a texture combine operation with the GL_DOT3_RGB mode.
Avoid Storing Constants in Attribute Arrays
If your models include attributes that uses data that remains constant across the entire model, do not duplicate
that data for each vertex. OpenGL ES 2.0 and 3.0 apps can either set a constant vertex attribute or use a uniform
shader value to hold the value instead. OpenGL ES 1.1 app should use a per-vertex attribute function such as
glColor4ub or glTexCoord2f instead.
Use the Smallest Acceptable Types for Attributes
When specifying the size of each of your attribute’s components, choose the smallest data type that provides
acceptable results. Here are some guidelines:
●
Specify vertex colors using four unsigned byte components (GL_UNSIGNED_BYTE).
●
Specify texture coordinates using 2 or 4 unsigned bytes (GL_UNSIGNED_BYTE) or unsigned short
(GL_UNSIGNED_SHORT). Do not pack multiple sets of texture coordinates into a single attribute.
●
Avoid using the OpenGL ES GL_FIXED data type. It requires the same amount of memory as GL_FLOAT,
but provides a smaller range of values. All iOS devices support hardware floating-point units, so floating
point values can be processed more quickly.
●
OpenGL ES 3.0 contexts support a wider range of small data types, such as GL_HALF_FLOAT and
GL_INT_2_10_10_10_REV. These often provide sufficient precision for attributes such as normals, with
a smaller footprint than GL_FLOAT.
If you specify smaller components, be sure you reorder your vertex format to avoid misaligning your vertex
data. See “Avoid Misaligned Vertex Data” (page 70).
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
69
Best Practices for Working with Vertex Data
Use Interleaved Vertex Data
Use Interleaved Vertex Data
You can specify vertex data as a series of arrays (also known as a struct of arrays ) or as an array where each
element includes multiple attributes (an array of structs ). The preferred format on iOS is an array of structs
with a single interleaved vertex format. Interleaved data provides better memory locality for each vertex.
Figure 9-2
Interleaved memory structures place all data for a vertex together in memory
An exception to this rule is when your app needs to update some vertex data at a rate different from the rest
of the vertex data, or if some data can be shared between two or more models. In either case, you may want
to separate the attribute data into two or more structures.
Figure 9-3
Use multiple vertex structures when some data is used differently
Avoid Misaligned Vertex Data
When you are designing your vertex structure, align the beginning of each attribute to an offset that is either
a multiple of its component size or 4 bytes, whichever is larger. When an attribute is misaligned, iOS must
perform additional processing before passing the data to the graphics hardware.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
70
Best Practices for Working with Vertex Data
Use Triangle Strips to Batch Vertex Data
In Figure 9-4 (page 71), the position and normal data are each defined as three short integers, for a total of
six bytes. The normal data begins at offset 6, which is a multiple of the native size (2 bytes), but is not a multiple
of 4 bytes. If this vertex data were submitted to iOS, iOS would have to take additional time to copy and align
the data before passing it to the hardware. To fix this, explicitly add two bytes of padding after each attribute.
Figure 9-4
Align Vertex Data to avoid additional processing
Use Triangle Strips to Batch Vertex Data
Using triangle strips significantly reduces the number of vertex calculations that OpenGL ES must perform on
your models. On the left side of Figure 9-5, three triangles are specified using a total of nine vertices. C, E and
G actually specify the same vertex! By specifying the data as a triangle strip, you can reduce the number of
vertices from nine to five.
Figure 9-5
Triangle strip
Sometimes, your app can combine more than one triangle strip into a single larger triangle strip. All of the
strips must share the same rendering requirements. This means:
●
You must use the same shader to draw all of the triangle strips.
●
You must be able to render all of the triangle strips without changing any OpenGL state.
●
The triangle strips must share the same vertex attributes.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
71
Best Practices for Working with Vertex Data
Use Triangle Strips to Batch Vertex Data
To merge two triangle strips, duplicate the last vertex of the first strip and the first vertex of the second strip,
as shown in Figure 9-6. When this strip is submitted to OpenGL ES, triangles DEE, EEF, EFF, and FFG are
considered degenerate and not processed or rasterized.
Figure 9-6
Use degenerate triangles to merge triangle strips
For best performance, your models should be submitted as a single indexed triangle strip. To avoid specifying
data for the same vertex multiple times in the vertex buffer, use a separate index buffer and draw the triangle
strip using the glDrawElements function (or the glDrawElementsInstanced or glDrawRangeElements
functions, if appropriate).
In OpenGL ES 3.0, you can use the primitive restart feature to merge triangle strips without using degenerate
triangles. When this feature is enabled, OpenGL ES treats the largest possible value in an index buffer as a
command to finish one triangle strip and start another. Listing 9-1 demonstrates this approach.
Listing 9-1
Using primitive restart in OpenGL ES 3.0
// Prepare index buffer data (not shown: vertex buffer data, loading vertex and
index buffers)
GLushort indexData[11] = {
0, 1, 2, 3, 4,
0xFFFF,
// triangle strip ABCDE
// primitive restart index (largest possible GLushort value)
5, 6, 7, 8, 9,
// triangle strip FGHIJ
};
// Draw triangle strips
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
glDrawElements(GL_TRIANGLE_STRIP, 11, GL_UNSIGNED_SHORT, 0);
Where possible, sort vertex and index data so triangles that share common vertices are drawn reasonably close
to each other in the triangle strip. Graphics hardware often caches recent vertex calculations to avoid
recalculating a vertex.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
72
Best Practices for Working with Vertex Data
Use Vertex Buffer Objects to Manage Copying Vertex Data
Use Vertex Buffer Objects to Manage Copying Vertex Data
Listing 9-2 provides a function that a simple app might use to provide position and color data to the vertex
shader. It enables two attributes and configures each to point at the interleaved vertex structure. Finally, it
calls the glDrawElements function to render the model as a single triangle strip.
Listing 9-2
Submitting vertex data to a shader program
typedef struct _vertexStruct
{
GLfloat position[2];
GLubyte color[4];
} vertexStruct;
enum {
ATTRIB_POSITION,
ATTRIB_COLOR,
NUM_ATTRIBUTES };
void DrawModel()
{
const vertexStruct vertices[] = {...};
const GLubyte indices[] = {...};
glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE,
sizeof(vertexStruct), &vertices[0].position);
glEnableVertexAttribArray(ATTRIB_POSITION);
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE,
sizeof(vertexStruct), &vertices[0].color);
glEnableVertexAttribArray(ATTRIB_COLOR);
glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte),
GL_UNSIGNED_BYTE, indices);
}
This code works, but is inefficient. Each time DrawModel is called, the index and vertex data are copied to
OpenGL ES, and transferred to the graphics hardware. If the vertex data does not change between invocations,
these unnecessary copies can impact performance. To avoid unnecessary copies, your app should store its
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
73
Best Practices for Working with Vertex Data
Use Vertex Buffer Objects to Manage Copying Vertex Data
vertex data in a vertex buffer object (VBO). Because OpenGL ES owns the vertex buffer object’s memory, it
can store the buffer in memory that is more accessible to the graphics hardware, or pre-process the data into
the preferred format for the graphics hardware.
Note: When using vertex array objects in OpenGL ES 3.0, you must also use vertex buffer objects.
Listing 9-3 creates a pair of vertex buffer objects, one to hold the vertex data and the second for the strip’s
indices. In each case, the code generates a new object, binds it to be the current buffer, and fills the buffer.
CreateVertexBuffers would be called when the app is initialized.
Listing 9-3
Creating vertex buffer objects
GLuint
vertexBuffer;
GLuint
indexBuffer;
void CreateVertexBuffers()
{
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
Listing 9-4 modifies Listing 9-2 (page 73) to use the vertex buffer objects. The key difference in Listing 9-4 is
that the parameters to the glVertexPointer and glColorPointer functions no longer point to the vertex
arrays. Instead, each is an offset into the vertex buffer object.
Listing 9-4
Drawing using Vertex Buffer Objects
void DrawModelUsingVertexBuffers()
{
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
74
Best Practices for Working with Vertex Data
Use Vertex Buffer Objects to Manage Copying Vertex Data
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE,
sizeof(vertexStruct), (void*)offsetof(vertexStruct,position));
glEnableVertexAttribArray(ATTRIB_POSITION);
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE,
sizeof(vertexStruct), (void*)offsetof(vertexStruct,color));
glEnableVertexAttribArray(ATTRIB_COLOR);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte),
GL_UNSIGNED_BYTE, (void*)0);
}
Buffer Usage Hints
The previous example initialized the vertex buffer once and never changed its contents afterwards. You can
change the contents of a vertex buffer. A key part of the design of vertex buffer objects is that the app can
inform OpenGL ES how it uses the data stored in the buffer. An OpenGL ES implementation can use this hint
to alter the strategy it uses for storing the vertex data. In Listing 9-3 (page 74), each call to the glBufferData
function provides a usage hint as the last parameter. Passing GL_STATIC_DRAW into glBufferData tells
OpenGL ES that the contents of both buffers are never expected to change, which gives OpenGL ES more
opportunities to optimize how and where the data is stored.
The OpenGL ES specification defines the following usage cases:
●
GL_STATIC_DRAW is for vertex buffers that are rendered many times, and whose contents are specified
once and never change.
●
GL_DYNAMIC_DRAW is for vertex buffers that are rendered many times, and whose contents change during
the rendering loop.
●
GL_STREAM_DRAW is for vertex buffers that are rendered a small number of times and then discarded.
In iOS, GL_DYNAMIC_DRAW and GL_STREAM_DRAW are equivalent. You can use the glBufferSubData function
to update buffer contents, but doing so incurs a performance penalty because it flushes the command buffer
and waits for all commands to complete. Double or triple buffering can reduce this performance cost somewhat.
(See “Use Double Buffering to Avoid Resource Conflicts” (page 51).) For better performance, use the
glMapBufferRange function in OpenGL ES 3.0 or the corresponding function provided by the
EXT_map_buffer_range extension in OpenGL ES 2.0 or 1.1.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
75
Best Practices for Working with Vertex Data
Use Vertex Buffer Objects to Manage Copying Vertex Data
If different attributes inside your vertex format require different usage patterns, split the vertex data into
multiple structures and allocate a separate vertex buffer object for each collection of attributes that share
common usage characteristics. Listing 9-5 modifies the previous example to use a separate buffer to hold the
color data. By allocating the color buffer using the GL_DYNAMIC_DRAW hint, OpenGL ES can allocate that buffer
so that your app maintains reasonable performance.
Listing 9-5
Drawing a model with multiple vertex buffer objects
typedef struct _vertexStatic
{
GLfloat position[2];
} vertexStatic;
typedef struct _vertexDynamic
{
GLubyte color[4];
} vertexDynamic;
// Separate buffers for static and dynamic data.
GLuint
staticBuffer;
GLuint
dynamicBuffer;
GLuint
indexBuffer;
const vertexStatic staticVertexData[] = {...};
vertexDynamic dynamicVertexData[] = {...};
const GLubyte indices[] = {...};
void CreateBuffers()
{
// Static position data
glGenBuffers(1, &staticBuffer);
glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(staticVertexData), staticVertexData,
GL_STATIC_DRAW);
// Dynamic color data
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
76
Best Practices for Working with Vertex Data
Consolidate Vertex Array State Changes Using Vertex Array Objects
// While not shown here, the expectation is that the data in this buffer changes
between frames.
glGenBuffers(1, &dynamicBuffer);
glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(dynamicVertexData), dynamicVertexData,
GL_DYNAMIC_DRAW);
// Static index data
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
void DrawModelUsingMultipleVertexBuffers()
{
glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE,
sizeof(vertexStruct), (void*)offsetof(vertexStruct,position));
glEnableVertexAttribArray(ATTRIB_POSITION);
glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE,
sizeof(vertexStruct), (void*)offsetof(vertexStruct,color));
glEnableVertexAttribArray(ATTRIB_COLOR);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte),
GL_UNSIGNED_BYTE, (void*)0);
}
Consolidate Vertex Array State Changes Using Vertex Array Objects
Take a closer look at the DrawModelUsingMultipleVertexBuffers function in Listing 9-5 (page 76). It
enables many attributes, binds multiple vertex buffer objects, and configures attributes to point into the buffers.
All of that initialization code is essentially static; none of the parameters change from frame to frame. If this
function is called every time the app renders a frame, there’s a lot of unnecessary overhead reconfiguring the
graphics pipeline. If the app draws many different kinds of models, reconfiguring the pipeline may become a
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
77
Best Practices for Working with Vertex Data
Consolidate Vertex Array State Changes Using Vertex Array Objects
bottleneck. Instead, use a vertex array object to store a complete attribute configuration. Vertex array objects
are part of the core OpenGL ES 3.0 specification and are available in OpenGL ES 2.0 and 1.1 using the
OES_vertex_array_object extension.
Figure 9-7 shows an example configuration with two vertex array objects. Each configuration is independent
of the other; each vertex array object can reference a different set of vertex attributes, which can be stored in
the same vertex buffer object or split across several vertex buffer objects.
Figure 9-7
Vertex array object configuration
Listing 9-6 provides the code used to configure first vertex array object shown above. It generates an identifier
for the new vertex array object and then binds the vertex array object to the context. After this, it makes the
same calls to configure vertex attributes as it would if the code were not using vertex array objects. The
configuration is stored to the bound vertex array object instead of to the context.
Listing 9-6
Configuring a vertex array object
void ConfigureVertexArrayObject()
{
// Create and bind the vertex array object.
glGenVertexArraysOES(1,&vao1);
glBindVertexArrayOES(vao1);
// Configure the attributes in the VAO.
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glVertexAttribPointer(ATT_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(staticFmt),
(void*)offsetof(staticFmt,position));
glEnableVertexAttribArray(ATT_POSITION);
glVertexAttribPointer(ATT_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE,
sizeof(staticFmt), (void*)offsetof(staticFmt,texcoord));
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
78
Best Practices for Working with Vertex Data
Consolidate Vertex Array State Changes Using Vertex Array Objects
glEnableVertexAttribArray(ATT_TEXCOORD);
glVertexAttribPointer(ATT_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof(staticFmt),
(void*)offsetof(staticFmt,normal));
glEnableVertexAttribArray(ATT_NORMAL);
glBindBuffer(GL_ARRAY_BUFFER, vbo2);
glVertexAttribPointer(ATT_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE,
sizeof(dynamicFmt), (void*)offsetof(dynamicFmt,color));
glEnableVertexAttribArray(ATT_COLOR);
// Bind back to the default state.
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArrayOES(0); }
To draw, the code binds the vertex array object and then submits drawing commands as before.
Note: Vertex arrays are part of the core OpenGL ES 3.0 specification. In an OpenGL ES 3.0 context,
use the glGenVertexArrays and glBindVertexArray functions. When using vertex array objects
in OpenGL ES 3.0, you must also use vertex buffer objects.
For best performance, your app should configure each vertex array object once, and never change it at runtime.
If you need to change a vertex array object in every frame, create multiple vertex array objects instead. For
example, an app that uses double buffering might configure one set of vertex array objects for odd-numbered
frames, and a second set for even numbered frames. Each set of vertex array objects would point at the vertex
buffer objects used to render that frame. When a vertex array object’s configuration does not change, OpenGL ES
can cache information about the vertex format and improve how it processes those vertex attributes.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
79
Best Practices for Working with Texture Data
Texture data is often the largest portion of the data your app uses to render a frame; textures provide the detail
required to present great images to the user. To get the best possible performance out of your app, manage
your app’s textures carefully. To summarize the guidelines:
●
Create your textures when your app is initialized, and never change them in the rendering loop.
●
Reduce the amount of memory your textures use.
●
Combine smaller textures into a larger texture atlas.
●
Use mipmaps to reduce the bandwidth required to fetch texture data.
●
Use multitexturing to perform texturing operations in a single pass.
Load Textures During Initialization
Creating and loading textures is an expensive operation. For best results, avoid creating new textures while
your app is running. Instead, create and load your texture data during initialization.
After you create a texture, avoid changing it except at the beginning or end of a frame. Currently, all iOS devices
use a tile-based deferred renderer, making calls to the glTexSubImage and glCopyTexSubImage functions
particularly expensive. See “Tile-Based Deferred Rendering” in OpenGL ES Hardware Platform Guide for iOS for
more information.
Use the GLKit Framework to Load Texture Data
Loading texture data is a fundamental operation that is important to get right. Using the GLKit framework, the
GLKTextureLoader class makes creating and loading new textures easy. The GLKTextureLoader class can
load texture data from a variety of sources, including files, URLs, in-memory representations, and CGImages.
Regardless of the input source, the GLKTextureLoader class creates and loads a new texture from data and
returns the texture information as a GLKTextureInfo object. Properties of GLKTextureInfo objects can be
accessed to perform various tasks, including binding the texture to a context and enabling it for drawing.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
80
Best Practices for Working with Texture Data
Load Textures During Initialization
Note: A GLKTextureInfo object does not own the OpenGL ES texture object it describes. You
must call the glDeleteTextures function to dispose of texture objects when you are done using
them.
Listing 10-1 presents a typical strategy to load a new texture from a file and to bind and enable the texture
for later use.
Listing 10-1 Loading a two-dimensional texture from a file
GLKTextureInfo *spriteTexture;
NSError *theError;
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Sprite" ofType:@"png"];
// 1
spriteTexture = [GLKTextureLoader textureWithContentsOfFile:filePath options:nil
error:&theError]; // 2
glBindTexture(spriteTexture.target, spriteTexture.name); // 3
glEnable(spriteTexture.target); // 4
Here is what the code does, corresponding to the numbered steps in the listing:
1.
Create a path to the image that contains the texture data. This path is passed as a parameter to the
GLKTextureLoader class method textureWithContentsOfFile:options:error:.
2.
Load a new texture from the image file and store the texture information in a GLKTextureInfo object.
There are a variety of texture loading options available. For more information, see GLKTextureLoader Class
Reference .
3.
Bind the texture to a context, using the appropriate properties of the GLKTextureInfo object as
parameters.
4.
Enable use of the texture for drawing using the appropriate property of the GLKTextureInfo object as
a parameter.
The GLKTextureLoader class can also load cubemap textures in most common image formats. And, if your
app needs to load and create new textures while running, the GLKTextureLoader class also provides methods
for asynchronous texture loading. See GLKTextureLoader Class Reference for more information.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
81
Best Practices for Working with Texture Data
Reduce Texture Memory Usage
Reduce Texture Memory Usage
Reducing the amount of memory your iOS app uses is always an important part of tuning your app. That said,
an OpenGL ES app is also constrained in the total amount of memory it can use to load textures. Where possible,
your app should always try to reduce the amount of memory it uses to hold texture data. Reducing the memory
used by a texture is almost always at the cost of image quality, so you must balance any changes your app
makes to its textures with the quality level of the final rendered frame. For best results, try the techniques
described below, and choose the one that provides the best memory savings at an acceptable quality level.
Compress Textures
Texture compression usually provides the best balance of memory savings and quality. OpenGL ES for iOS
supports multiple compressed texture formats.
All iOS devices support the the PowerVR Texture Compression (PVRTC) format by implementing the
GL_IMG_texture_compression_pvrtc extension. There are two levels of PVRTC compression, 4 bits per channel
and 2 bits per channel, which offer a 8:1 and 16:1 compression ratio over the uncompressed 32-bit texture
format respectively. A compressed PVRTC texture still provides a decent level of quality, particularly at the 4-bit
level. For more information on compressing textures into PVRTC format, see “Using texturetool to Compress
Textures” (page 92).
OpenGL ES 3.0–capable iOS devices also support the ETC2 and EAC compressed texture formats.
Use Lower-Precision Color Formats
If your app cannot use compressed textures, consider using a lower precision pixel format. A texture in RGB565,
RGBA5551, or RGBA4444 format uses half the memory of a texture in RGBA8888 format. Use RGBA8888 only
when your app needs that level of quality.
Use Properly Sized Textures
The images that an iOS-based device displays are very small. Your app does not need to provide large textures
to present acceptable images to the screen. Halving both dimensions of a texture reduces the amount of
memory needed for that texture to one-quarter that of the original texture.
Before shrinking your textures, attempt to compress the texture or use a lower-precision color format first. A
texture compressed with the PVRTC format usually provides higher image quality than shrinking the
texture—and it uses less memory too!
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
82
Best Practices for Working with Texture Data
Combine Textures into Texture Atlases
Combine Textures into Texture Atlases
Binding to a texture takes time for OpenGL ES to process. Apps that reduce the number of changes they make
to OpenGL ES state perform better. For textures, one way to avoid binding to new textures is to combine
multiple smaller textures into a single large texture, known as a texture atlas. When you use a texture atlas,
you can bind a single texture and then make multiple drawing calls that use that texture, or even coalesce
multiple drawing calls into a single draw call. The texture coordinates provided in your vertex data are modified
to select the smaller portion of the texture from within the atlas.
Texture atlases have a few restrictions:
●
You cannot use a texture atlas if you are using the GL_REPEAT texture wrap parameter.
●
Filtering may sometimes fetch texels outside the expected range. To use those textures in a texture atlas,
you must place padding between the textures that make up the texture atlas.
●
Because the texture atlas is still a texture, it is subject to the OpenGL ES implementation’s maximum texture
size as well as other texture attributes.
Xcode 5 can automatically build texture atlases for you from a collection of images. For details on creating a
texture atlas, see Texture Atlas Help. This feature is provided primarily for developers using the Sprite Kit
framework, but any app can make use of the texture atlas files it produces. For each .atlas folder in your
project, Xcode creates a .atlasc folder in your app bundle, containing one or more compiled atlas images
and a property list (.plist) file. The property list file describes the individual images that make up the atlas and
their locations within the atlas image—you can use this information to calculate appropriate texture coordinates
for use in OpenGL ES drawing.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
83
Best Practices for Working with Texture Data
Use Mipmapping to Reduce Memory Bandwidth Usage
Use Mipmapping to Reduce Memory Bandwidth Usage
Your app should provide mipmaps for all textures except those being used to draw 2D unscaled images.
Although mipmaps use additional memory, they prevent texturing artifacts and improve image quality. More
importantly, when the smaller mipmaps are sampled, fewer texels are fetched from texture memory which
reduces the memory bandwidth needed by the graphics hardware, improving performance.
The GL_LINEAR_MIPMAP_LINEAR filter mode provides the best quality when texturing but requires additional
texels to be fetched from memory. Your app can trade some image quality for better performance by specifying
the GL_LINEAR_MIPMAP_NEAREST filter mode instead.
When combining mip maps with texture atlases, use the APPLE_texture_max_level extension to control how
your textures are filtered.
Use Multitexturing Instead of Multiple Passes
Many apps perform multiple passes to draw a model, altering the configuration of the graphics pipeline for
each pass. This not only requires additional time to reconfigure the graphics pipeline, but it also requires vertex
information to be reprocessed for every pass, and pixel data to be read back from the framebuffer on later
passes.
All OpenGL ES implementations on iOS support at least two texture units, and most devices support at least
eight. Your app should use these texture units to perform as many steps as possible in your algorithm in each
pass. You can retrieve the number of texture units available to your app by calling the glGetIntegerv function,
passing in GL_MAX_TEXTURE_UNITS as the parameter.
If your app requires multiple passes to render a single object:
●
Ensure that the position data remains unchanged for every pass.
●
On the second and later stage, test for pixels that are on the surface of your model by calling the
glDepthFunc function with GL_EQUAL as the parameter.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
84
Best Practices for Shaders
Shaders provide great flexibility, but they can also be a significant bottleneck if you perform too many
calculations or perform them inefficiently.
Compile and Link Shaders During Initialization
Creating a shader program is an expensive operation compared to other OpenGL ES state changes. Compile,
link, and validate your programs when your app is initialized. Once you’ve created all your shaders, the app
can efficiently switch between them by calling glUseProgram.
Check for Shader Program Errors When Debugging
Reading diagnostic information after compiling or linking a shader program is not necessary in a Release build
of your app and can reduce performance. Use OpenGL ES functions to read shader compile or link logs only
in development builds of your app, as shown in Listing 11-1.
Listing 11-1 Read shader compile/link logs only in development builds
// After calling glCompileShader, glLinkProgram, or similar
#ifdef DEBUG
// Check the status of the compile/link
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLen);
if(logLen > 0) {
// Show any errors as appropriate
glGetProgramInfoLog(prog, logLen, &logLen, log);
fprintf(stderr, "Prog Info Log: %s\n", log);
}
#endif
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
85
Best Practices for Shaders
Compile and Link Shaders During Initialization
Similarly, you should call the glValidateProgram function only in development builds. You can use this
function to find development errors such as failing to bind all texture units required by a shader program. But
becausevalidating a program checks it against the entire OpenGL ES context state, it is an expensive operation.
Since the results of program validation are only meaningful during development, you should not call this
function in Release builds of your app.
Use Separate Shader Objects to Speed Compilation and Linking
Many OpenGL ES apps use several vertex and fragment shaders, and it is often useful to reuse the same
fragment shader with different vertex shaders or vice versa. Because the core OpenGL ES specification requires
a vertex and fragment shader to be linked together in a single shader program, mixing and matching shaders
results in a large number of programs, increasing the total shader compile and link time when you initialize
your app.
OpenGL ES 2.0 and 3.0 contexts on iOS support the EXT_separate_shader_objects extension. You can use the
functions provided by this extension to compile vertex and fragment shaders separately, and to mix and match
precompiled shader stages at render time using program pipeline objects. Additionally, this extension provides
a simplified interface for compiling and using shaders, shown in Listing 11-2.
Listing 11-2 Compiling and using shaders with the EXT_separate_shader_objects extension
- (void)loadShaders
{
const GLchar *vertexSourceText = " ... vertex shader GLSL source code ... ";
const GLchar *fragmentSourceText = " ... fragment shader GLSL source code ...
";
// Compile and link the separate vertex shader program, then read its uniform
variable locations
_vertexProgram = glCreateShaderProgramvEXT(GL_VERTEX_SHADER, 1,
&vertexSourceText);
_uniformModelViewProjectionMatrix = glGetUniformLocation(_vertexProgram,
"modelViewProjectionMatrix");
_uniformNormalMatrix = glGetUniformLocation(_vertexProgram, "normalMatrix");
// Compile and link the separate fragment shader program (which uses no uniform
variables)
_fragmentProgram =
&fragmentSourceText);
glCreateShaderProgramvEXT(GL_FRAGMENT_SHADER, 1,
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
86
Best Practices for Shaders
Respect the Hardware Limits on Shaders
// Construct a program pipeline object and configure it to use the shaders
glGenProgramPipelinesEXT(1, &_ppo);
glBindProgramPipelineEXT(_ppo);
glUseProgramStagesEXT(_ppo, GL_VERTEX_SHADER_BIT_EXT, _vertexProgram);
glUseProgramStagesEXT(_ppo, GL_FRAGMENT_SHADER_BIT_EXT, _fragmentProgram);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
// Clear the framebuffer
glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Use the previously constructed program pipeline and set uniform contents
in shader programs
glBindProgramPipelineEXT(_ppo);
glProgramUniformMatrix4fvEXT(_vertexProgram, _uniformModelViewProjectionMatrix,
1, 0, _modelViewProjectionMatrix.m);
glProgramUniformMatrix3fvEXT(_vertexProgram, _uniformNormalMatrix, 1, 0,
_normalMatrix.m);
// Bind a VAO and render its contents
glBindVertexArrayOES(_vertexArray);
glDrawElements(GL_TRIANGLE_STRIP, _indexCount, GL_UNSIGNED_SHORT, 0);
}
Respect the Hardware Limits on Shaders
OpenGL ES limits the number of each variable type you can use in a vertex or fragment shader. The OpenGL ES
specification doesn’t require implementations to provide a software fallback when these limits are exceeded;
instead, the shader simply fails to compile or link. When developing your app you must ensure that no errors
occur during shader compilation, as shown in Listing 11-1.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
87
Best Practices for Shaders
Use Precision Hints
Use Precision Hints
Precision hints were added to the GLSL ES language specification to address the need for compact shader
variables that match the smaller hardware limits of embedded devices. Each shader must specify a default
precision; individual shader variables may override this precision to provide hints to the compiler on how that
variable is used in your app. An OpenGL ES implementation is not required to use the hint information, but
may do so to generate more efficient shaders. The GLSL ES specification lists the range and precision for each
hint.
Important: The range limits defined by the precision hints are not enforced. You cannot assume your data
is clamped to this range.
Follow these guidelines:
●
When in doubt, default to high precision.
●
Colors in the 0.0 to 1.0 range can usually be represented using low precision variables.
●
Position data should usually be stored as high precision.
●
Normals and vectors used in lighting calculations can usually be stored as medium precision.
●
After reducing precision, retest your app to ensure that the results are what you expect.
Listing 11-3 defaults to high precision variables, but calculates the color output using low precision variables
because higher precision is not necessary.
Listing 11-3 Low precision is acceptable for fragment color
default precision highp; // Default precision declaration is required in fragment
shaders.
uniform lowp sampler2D sampler; // Texture2D() result is lowp.
varying lowp vec4 color;
varying vec2 texCoord;
// Uses default highp precision.
void main()
{
gl_FragColor = color * texture2D(sampler, texCoord);
}
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
88
Best Practices for Shaders
Perform Vector Calculations Lazily
Perform Vector Calculations Lazily
Not all graphics processors include vector processors; they may perform vector calculations on a scalar processor.
When performing calculations in your shader, consider the order of operations to ensure that the calculations
are performed efficiently even if they are performed on a scalar processor.
If the code in Listing 11-4 were executed on a vector processor, each multiplication would be executed in
parallel across all four of the vector’s components. However, because of the location of the parenthesis, the
same operation on a scalar processor would take eight multiplications, even though two of the three parameters
are scalar values.
Listing 11-4 Poor use of vector operators
highp float f0, f1;
highp vec4 v0, v1;
v0 = (v1 * f0) * f1;
The same calculation can be performed more efficiently by shifting the parentheses as shown in Listing 11-5.
In this example, the scalar values are multiplied together first, and the result multiplied against the vector
parameter; the entire operation can be calculated with five multiplications.
Listing 11-5 Proper use of vector operations
highp float f0, f1;
highp vec4 v0, v1;
v0 = v1 * (f0 * f1);
Similarly, your app should always specify a write mask for a vector operation if it does not use all of the
components of the result. On a scalar processor, calculations for components not specified in the mask can be
skipped. Listing 11-6 runs twice as fast on a scalar processor because it specifies that only two components
are needed.
Listing 11-6 Specifying a write mask
highp vec4 v0;
highp vec4 v1;
highp vec4 v2;
v2.xz = v0 * v1;
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
89
Best Practices for Shaders
Use Uniforms or Constants Instead of Computing Values in a Shader
Use Uniforms or Constants Instead of Computing Values in a Shader
Whenever a value can be calculated outside the shader, pass it into the shader as a uniform or a constant.
Recalculating dynamic values can potentially be very expensive in a shader.
Avoid Branching
Branches are discouraged in shaders, as they can reduce the ability to execute operations in parallel on 3D
graphics processors. If your shaders must use branches, follow these recommendations:
●
Best performance: Branch on a constant known when the shader is compiled.
●
Acceptable: Branch on a uniform variable.
●
Potentially slow: Branching on a value computed inside the shader.
Instead of creating a large shader with many knobs and levers, create smaller shaders specialized for specific
rendering tasks. There is a tradeoff between reducing the number of branches in your shaders and increasing
the number of shaders you create. Test different options and choose the fastest solution.
Eliminate Loops
You can eliminate many loops by either unrolling the loop or using vectors to perform operations. For example,
this code is very inefficient:
// Loop
int i;
float f;
vec4 v;
for(i = 0; i < 4; i++)
v[i] += f;
The same operation can be done directly using a component-wise add:
float f;
vec4 v;
v += f;
When you cannot eliminate a loop, it is preferred that the loop have a constant limit to avoid dynamic branches.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
90
Best Practices for Shaders
Use Uniforms or Constants Instead of Computing Values in a Shader
Avoid Computing Array Indices in Shaders
Using indices computed in the shader is more expensive than a constant or uniform array index. Accessing
uniform arrays is usually cheaper than accessing temporary arrays.
Be Aware of Dynamic Texture Lookups
Dynamic texture lookups, also known as dependent texture reads , occur when a fragment shader computes
texture coordinates rather than using the unmodified texture coordinates passed into the shader. Dependent
texture reads are supported at no performance cost on OpenGL ES 3.0–capable hardware; on other devices,
dependent texture reads can delay loading of texel data, reducing performance. When a shader has no
dependent texture reads, the graphics hardware may prefetch texel data before the shader executes, hiding
some of the latency of accessing memory.
Listing 11-7 shows a fragment shader that calculates new texture coordinates. The calculation in this example
can easily be performed in the vertex shader, instead. By moving the calculation to the vertex shader and
directly using the vertex shader’s computed texture coordinates, you avoid the dependent texture read.
Note: It may not seem obvious, but any calculation on the texture coordinates counts as a dependent
texture read. For example, packing multiple sets of texture coordinates into a single varying parameter
and using a swizzle command to extract the coordinates still causes a dependent texture read.
Listing 11-7 Dependent Texture Read
varying vec2 vTexCoord;
uniform sampler textureSampler;
void main()
{
vec2 modifiedTexCoord = vec2(1.0 - vTexCoord.x, 1.0 - vTexCoord.y);
gl_FragColor = texture2D(textureSampler, modifiedTexCoord);
}
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
91
Using texturetool to Compress Textures
The iOS SDK includes a tool to compress your textures into the PVR texture compression format, aptly named
texturetool. If you have Xcode installed with the iOS 7.0 SDK, then texturetool is located at:
/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/texturetool.
texturetool provides various compression options, with tradeoffs between image quality and size. You need
to experiment with each texture to determine which setting provides the best compromise.
Note: The encoders, formats, and options available with texturetool are subject to change. This
document describes those options available as of iOS 7.
texturetool Parameters
The parameters that may be passed to texturetool are described in the rest of this section.
user$ texturetool -h
Usage: texturetool [-hl]
texturetool -c <reference_image> <input_image>
texturetool [-ms] [-e <encoder>] [-p <preview_file>] -o <output> [-f <format>]
<input_image>
first form:
-h
-l
Display this help menu.
List available encoders, individual encoder options, and file formats.
second form:
-c
Compare <input_image> to <reference_image> and report differences.
third form:
-m
Generate a complete mipmap chain from the input image.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
92
Using texturetool to Compress Textures
texturetool Parameters
-s
encoded image.
Report numerical differences between <input_image> and the
-e <encoder>
Encode texture levels with <encoder>.
-p <preview_file> Output a PNG preview of the encoded output to <preview_file>.
Requires -e option
-o <output>
Write processed image to <output>.
-f <format>
Set file <format> for <output> image.
Note: The -p option indicates that it requires the -e option. It also requires the -o option.
Listing A-1
Encoding options
user$ texturetool -l
Encoders:
PVRTC
--channel-weighting-linear
--channel-weighting-perceptual
--bits-per-pixel-2
--bits-per-pixel-4
--alpha-is-independent
--alpha-is-opacity
--punchthrough-unused
--punchthrough-allowed
--punchthrough-forced
Formats:
Raw
PVR
texturetool defaults to --bits-per-pixel-4, --channel-weighting-linear and -f Raw if no other
options are provided.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
93
Using texturetool to Compress Textures
texturetool Parameters
The --bits-per-pixel-2 and --bits-per-pixel-4 options create PVRTC data that encodes source pixels
into 2 or 4 bits per pixel. These options represent a fixed 16:1 and 8:1 compression ratio over the uncompressed
32-bit RGBA image data. There is a minimum data size of 32 bytes; the compressor never produces files smaller
than this, and at least that many bytes are expected when uploading compressed texture data.
When compressing, specifying --channel-weighting-linear spreads compression error equally across
all channels. By contrast, specifying --channel-weighting-perceptual attempts to reduce error in the
green channel compared to the linear option. In general, PVRTC compression does better with photographic
images than with line art.
The -m option automatically generates mipmap levels for the source image. These levels are provided as
additional image data in the archive created. If you use the Raw image format, then each level of image data
is appended one after another to the archive. If you use the PVR archive format, then each mipmap image is
provided as a separate image in the archive.
The (-f) parameter controls the format of its output file. The default format is Raw. This format is raw compressed
texture data, either for a single texture level (without the -m option) or for each texture level concatenated
together (with the -m option). Each texture level stored in the file is at least 32 bytes in size and must be
uploaded to the GPU in its entirety. The PVR format matches the format used by the PVRTexTool found in
Imagination Technologies’s PowerVR SDK. Your app must parse the data header to obtain the actual texture
data. See the PVRTextureLoader sample for an example of working with texture data in the PVR format.
The -s and -c options print error metrics during encoding. The -s option compares the input (uncompressed)
image to the output (encoded) image, and the -c option compares any two images. Results of the comparison
include root-mean-square error (rms), perceptually weighted pRms, worst-case texel error (max), and
compositing-based versions of each statistic (rmsC, pRmsC, and maxC). Compositing-based errors assume that
the image’s alpha channel is used for opacity and that the color in each texel is blended with the worst-case
destination color.
The error metrics used with the -s and -c options and by the encoder when optimizing a compressed image
treat the image’s alpha channel as an independent channel by default (or when using the
--alpha-is-independent option). The --alpha-is-opacity option changes the error metric to one
based on a standard blending operation, as implemented by calling glBlendFunc( GL_SRC_ALPHA,
GL_ONE_MINUS_SRC_ALPHA ).
PVR Texture compression supports a special punchthrough mode which can be enabled on a per 4x4 block
basis. This mode limits the color gradations that can be used within the block, but introduces the option of
forcing the pixel’s alpha value to 0. It can defeat PVRTC smooth color interpolation, introducing block boundary
artifacts, so it should be used with care. The three punchthrough options are:
●
--punchthrough-unused — No punchthrough (the default option).
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
94
Using texturetool to Compress Textures
texturetool Parameters
--punchthrough-allowed — The encoder may enable punchthrough on a block by block basis when
●
optimizing for image quality. This option generally improves the objective (per-pixel) error metrics used
by the compression algorithm, but may introduce subjective artifacts.
--punchthrough-forced — Punchthrough is enabled on every block, limiting color gradation but
●
making it possible for any pixel in the block to have an alpha of 0. This option is provided principally for
completeness, but may be useful when the results can be compared visually to the other options.
Important: Source images for the encoder must satisfy these requirements:
●
Height and width must be at least 8.
●
Height and width must be a power of 2.
●
Must be square (height==width).
●
Source images must be in a format that Image IO accepts in OS X. For best results, your original textures
should begin in an uncompressed data format.
Important: If you are using PVRTexTool to compress your textures, then you must create textures that are
square and a power of 2 in length. If your app attempts to load a non-square or non-power-of-two texture
in iOS, an error is returned.
Listing A-2
Encoding images into the PVRTC compression format
Encode Image.png into PVRTC using linear weights and 4 bpp, and saving as
ImageL4.pvrtc
user$ texturetool -e PVRTC --channel-weighting-linear --bits-per-pixel-4 -o
ImageL4.pvrtc Image.png
Encode Image.png into PVRTC using perceptual weights and 4 bpp, and saving as
ImageP4.pvrtc
user$ texturetool -e PVRTC --channel-weighting-perceptual --bits-per-pixel-4 -o
ImageP4.pvrtc Image.png
Encode Image.png into PVRTC using linear weights and 2 bpp, and saving as
ImageL2.pvrtc
user$ texturetool -e PVRTC --channel-weighting-linear --bits-per-pixel-2 -o
ImageL2.pvrtc Image.png
Encode Image.png into PVRTC using perceptual weights and 2 bpp, and saving as
ImageP2.pvrtc
user$ texturetool -e PVRTC --channel-weighting-perceptual --bits-per-pixel-2 -o
ImageP2.pvrtc Image.png
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
95
Using texturetool to Compress Textures
texturetool Parameters
Listing A-3
Encoding images into the PVRTC compression format while creating a preview
Encode Image.png into PVRTC using linear weights and 4 bpp, and saving the output
as ImageL4.pvrtc and a PNG preview as ImageL4.png
user$ texturetool -e PVRTC --channel-weighting-linear --bits-per-pixel-4 -o
ImageL4.pvrtc -p ImageL4.png Image.png
Encode Image.png into PVRTC using perceptual weights and 4 bpp, and saving the
output as ImageP4.pvrtc and a PNG preview as ImageP4.png
user$ texturetool -e PVRTC --channel-weighting-perceptual --bits-per-pixel-4 -o
ImageP4.pvrtc -p ImageP4.png Image.png
Encode Image.png into PVRTC using linear weights and 2 bpp, and saving the output
as ImageL2.pvrtc and a PNG preview as ImageL2.png
user$ texturetool -e PVRTC --channel-weighting-linear --bits-per-pixel-2 -o
ImageL2.pvrtc -p ImageL2.png Image.png
Encode Image.png into PVRTC using perceptual weights and 2 bpp, and saving the
output as ImageP2.pvrtc and a PNG preview as ImageP2.png
user$ texturetool -e PVRTC --channel-weighting-perceptual --bits-per-pixel-2 -o
ImageP2.pvrtc -p ImageP2.png Image.png
Note: It is not possible to create a preview without also specifying the -o parameter and a valid
output file. Preview images are always in PNG format.
To load a PVR-compressed texture, use the GLKTextureLoader class or see the PVRTextureLoader sample.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
96
Document Revision History
This table describes the changes to OpenGL ES Programming Guide for iOS .
Date
Notes
2013-08-27
Updated to include more information about OpenGL ES 3.0, GLKit, and
the Xcode debugger.
2013-04-23
Moved the platform notes to OpenGL ES Hardware Platform Guide for
iOS.
Removed the “Platform Notes” chapter and moved the information into
its own book, OpenGL ES Hardware Platform Guide for iOS .
2011-02-24
Added information about new OpenGL ES tools provided in Xcode 4.
Clarified that context sharing can only be used when all of the contexts
share the same version of the OpenGL ES API.
2010-11-15
Significantly revised and expanded all the material in the document.
Added a glossary of commonly used graphics and OpenGL ES terminology.
Added a detailed explanation of the rendering loop, including
enhancements added in iOS 4 (renderbuffer discards).
2010-09-01
Fixed an incorrect link. Clarified some performance guidelines. Added
links to more new extensions added in iOS 4.
2010-07-09
Changed the title from "OpenGL ES Programming Guide for iPhone OS."
2010-06-14
Added new extensions exposed by iOS 4.
2010-01-20
Corrected code for creating a framebuffer object that draws to the screen.
2009-11-17
Minor updates and edits.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
97
Document Revision History
Date
Notes
2009-09-02
Edited for clarity. Updated extensions list to reflect what's currently
available. Clarified usage of triangle strips for best vertex performance.
Added a note to the platforms chapter about texture performance on the
PowerVR SGX.
2009-06-11
First version of a document that describes how to use the OpenGL ES 1.1
and 2.0 programming interfaces to create high performance graphics
within an iPhone Application.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
98
Glossary
completeness A state that indicates whether a
framebuffer object meets all the requirements for
drawing.
This glossary contains terms that are used specifically
for the Apple implementation of OpenGL ES as well
as terms that are common in OpenGL ES graphics
programming.
context A set of OpenGL ES state variables that
affect how drawing is performed to a drawable
object attached to that context. Also called a
rendering context .
aliased Said of graphics whose edges appear
jagged; can be remedied by performing antialiasing
operations.
culling Eliminating parts of a scene that can’t be
seen by the observer.
antialiasing In graphics, a technique used to
smooth and soften the jagged (or aliased) edges
that are sometimes apparent when graphical objects
such as text, line art, and images are drawn.
current context The rendering context to which
OpenGL ES routes commands issued by your app.
attach To establish a connection between two
existing objects. Compare bind.
current matrix A matrix used by OpenGL ES 1.1 to
transform coordinates in one system to those of
another system, such as the modelview matrix, the
perspective matrix, and the texture matrix. GLSL ES
uses user-defined matrices instead.
bind To create a new object and then establish a
connection between that object and a rendering
context. Compare attach.
depth In OpenGL, the z coordinate that specifies
how far a pixel lies from the observer.
bitmap A rectangular array of bits.
buffer A block of memory managed by OpenGL ES
dedicated to storing a specific kind of data, such as
vertex attributes, color data or indices.
depth buffer A block of memory used to store a
depth value for each pixel. The depth buffer is used
to determine whether or not a pixel can be seen by
the observer. All fragments rasterized by OpenGL ES
must pass a depth test that compares the incoming
depth value to the value stored in the depth buffer;
only fragments that pass the depth test are stored
to framebuffer.
clipping An operation that identifies the area of
drawing. Anything not in the clipping region is not
drawn.
clip coordinates The coordinate system used for
view-volume clipping. Clip coordinates are applied
after applying the projection matrix and prior to
perspective division.
double buffering The practice of using two buffers
to avoid resource conflicts between two different
parts of the graphic subsystem. The front buffer is
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
99
Glossary
used by one participant and the back buffer is
modified by the other. When a swap occurs, the front
and back buffer change places.
system framebuffer A framebuffer provided by an
operating system. This type of framebuffer supports
integrating OpenGL ES into an operating system’s
windowing system. iOS does not use system
framebuffers. Instead, it provides framebuffer objects
that are associated with a Core Animation layer.
drawable object An object allocated outside of
OpenGL ES that can be used as part of an OpenGL ES
framebuffer object. On iOS, the only type of drawable
object is the CAEAGLLayer class that integrates
OpenGL ES rendering into Core Animation.
framebuffer attachable image The rendering
destination for a framebuffer object.
framebuffer object A framebuffer that is managed
entirely by OpenGL ES. A framebuffer object contains
state information for an OpenGL ES framebuffer and
its set of images, called renderbuffers . Framebuffers
are built into OpenGL ES 2.0 and later, and all iOS
implementations of OpenGL ES 1.1 are guaranteed
to support framebuffer objects (through the
OES_framebuffer_object extension).
extension A feature of OpenGL ES that’s not part
of the OpenGL ES core API and therefore not
guaranteed to be supported by every
implementation of OpenGL ES. The naming
conventions used for extensions indicate how widely
accepted the extension is. The name of an extension
supported only by a specific company includes an
abbreviation of the company name. If more then
one company adopts the extension, the extension
name is changed to include EXT instead of a
company abbreviation. If the Khronos OpenGL
Working Group approves an extension, the extension
name changes to include OES instead of EXT or a
company abbreviation.
frustum The region of space that is seen by the
observer and that is warped by perspective division.
image A rectangular array of pixels.
interleaved data Arrays of dissimilar data that are
grouped together, such as vertex data and texture
coordinates. Interleaving can speed data retrieval.
eye coordinates The coordinate system with the
observer at the origin. Eye coordinates are produced
by the modelview matrix and passed to the
projection matrix.
mipmaps A set of texture maps, provided at various
resolutions, whose purpose is to minimize artifacts
that can occur when a texture is applied to a
geometric primitive whose onscreen resolution
doesn’t match the source texture map. Mipmapping
derives from the latin phrase multum in parvo , which
means “many things in a small place.”
filtering A process that modifies an image by
combining pixels or texels.
fog An effect achieved by fading colors to a
background color based on the distance from the
observer. Fog provides depth cues to the observer.
modelview matrix A 4 x 4 matrix used by OpenGL
to transform points, lines, polygons, and positions
from object coordinates to eye coordinates.
fragment The color and depth values calculated
when rasterizing a primitive. Each fragment must
past a series of tests before being blended with the
pixel stored in the framebuffer.
multisampling A technique that takes multiple
samples at a pixel and combines them with coverage
values to arrive at a final fragment.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
100
Glossary
mutex A mutual exclusion object in a multithreaded
app.
renderer A combination of hardware and software
that OpenGL ES uses to create an image from a view
and a model.
packing Converting pixel color components from
a buffer into the format needed by an app.
rendering context A container for state information.
pixel A picture element—the smallest element that
the graphics hardware can display on the screen. A
pixel is made up of all the bits at the location x , y ,
in all the bitplanes in the framebuffer.
rendering pipeline The order of operations used
by OpenGL ES to transform pixel and vertex data to
an image in the framebuffer.
render-to-texture An operation that draws content
directly to a texture target.
pixel depth In a pixel image, the number of bits
per pixel.
RGBA Red, green, blue, and alpha color
components.
pixel format A format used to store pixel data in
memory. The format describes the pixel components
(red, green, blue, alpha), the number and order of
components, and other relevant information, such
as whether a pixel contains stencil and depth values.
shader A program that computes surface properties.
shading language A high-level language, accessible
in C, used to produce advanced imaging effects.
premultiplied alpha A pixel whose other
components have been multiplied by the alpha
value. For example, a pixel whose RGBA values start
as (1.0, 0.5, 0.0, 0.5) would, when premultiplied, be
(0.5, 0.25, 0.0, 0.5).
stencil buffer Memory used specifically for stencil
testing. A stencil test is typically used to identify
masking regions, to identify solid geometry that
needs to be capped, and to overlap translucent
polygons.
primitives The simplest elements in
OpenGL—points, lines, polygons, bitmaps, and
images.
tearing A visual anomaly caused when part of the
current frame overwrites previous frame data in the
framebuffer before the current frame is fully
rendered on the screen. iOS avoids tearing by
processing all visible OpenGL ES content through
Core Animation.
projection matrix A matrix that OpenGL uses to
transform points, lines, polygons, and positions from
eye coordinates to clip coordinates.
tessellation An operation that reduces a surface to
a mesh of polygons, or a curve to a sequence of lines.
rasterization The process of converting vertex and
pixel data to fragments, each of which corresponds
to a pixel in the framebuffer.
texel A texture element used to specify the color
to apply to a fragment.
renderbuffer A rendering destination for a 2D pixel
image, used for generalized offscreen rendering, as
defined in the OpenGL specification for the
OES_framebuffer_object extension.
texture Image data used to modify the color of
rasterized fragments. The data can be one-, two-, or
three- dimensional or it can be a cube map.
texture mapping The process of applying a texture
to a primitive.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
101
Glossary
texture matrix A 4 x 4 matrix that OpenGL ES 1.1
uses to transform texture coordinates to the
coordinates that are used for interpolation and
texture lookup.
texture object An opaque data structure used to
store all data related to a texture. A texture object
can include such things as an image, a mipmap, and
texture parameters (width, height, internal format,
resolution, wrapping modes, and so forth).
vertex A three-dimensional point. A set of vertices
specify the geometry of a shape. Vertices can have
a number of additional attributes, such as color and
texture coordinates. See vertex array.
vertex array A data structure that stores a block of
data that specifies such things as vertex coordinates,
texture coordinates, surface normals, RGBA colors,
color indices, and edge flags.
vertex array object An OpenGL ES object that
records a list of active vertex attributes, the format
each attribute is stored in, and the location of the
data describing vertices and attributes. Vertex array
objects simplify the effort of reconfiguring the
graphics pipeline.
2013-08-27 | Copyright © 2013 Apple Inc. All Rights Reserved. Apple Confidential Information.
102
Apple Inc.
Copyright © 2013 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrieval system, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, Cocoa Touch,
Instruments, iPhone, Objective-C, Pages, Quartz,
Spaces, and Xcode are trademarks of Apple Inc.,
registered in the U.S. and other countries.
Retina is a trademark of Apple Inc.
OpenGL is a registered trademark of Silicon
Graphics, Inc.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE. AS A RESULT, THIS DOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement