Thursday, October 28, 2010

OpenGL ES 2.0 for iOS Chapter 2 - Meet OpenGL ES

Before we actually start working with OpenGLS, let's take a few minutes to talk about its history. The evolution of OpenGL has greatly affected the architecture and the design of the library. As a result, it's worth investing a few minutes to understand where it comes from; it will help you to use it better. If you've primarily or only worked in object-oriented programming languages like Objective-C and Java, OpenGL ES can seem a little strange and even old-fashioned since it is a procedural API, not an object-oriented one.

In the Beginning, There Was IRIS GL

In 1981, a company called Silicon Graphics, Inc. (often referred to by their initials, SGI) was founded and started making high-end 3D graphic workstations. All of the early models of SGI machines were given the name “IRIS” along with a model number. IRIS was actually an acronym that stood for Integrated Raster Imaging System; of course, the acronym had a dual meaning, since “iris” is also an anatomical term referring to part of the eye. Graphics programming on these workstations was done using a set of APIs called the IRIS Graphics Language, usually shortened to IRIS GL. Now, if you were programming in the early 1980s, you probably remember that things were very different. Even if you weren't programming back then, you likely realize just how long 30 years is in the fast-moving technology industry. In 1981, the Mac hadn't been introduced yet. Consumer-level computers were machines like the Apple //e, the IBM PC, and the Commodore 64, none of which had the horsepower to effectively work with 3D graphics in real time. Most computers didn't have floating point processors, and dedicated graphics processors were extremely rare; they wouldn't become common for nearly another decade. The IRIS workstations, on the other hand, used much more sophisticated hardware than these consumer machines and were specifically designed to handle the large number of floating point calculations needed in 3D graphics programming.

It wasn't just the hardware that was different back then. The state of computer programming was also very different. Although the concept of object-oriented programming existed, it wasn't in mainstream use yet, and most computers were programmed using procedural languages like C, Pascal, and BASIC, or else were written directly in assembly language. As a result, IRIS GL was a library of procedural function calls that could be used from those languages, with C being the most common.

What's in a Name?


IRIS GL is always written with a space between IRIS and GL and IRIS is always written in capital letters, because it's an acronym. On the other hand, OpenGL is written without any space between Open and GL and only the O, G, and L are capitalized.


The Open Gamble

A decade from its inception, SGI had established itself as the industry leader for sophisticated graphics programming. In 1992, SGI decided to do a massive overhaul and re-write of IRIS GL, and they also decided to do something rather extraordinary: allow their competitors to license the new version of their graphics library. Even more extraordinary was the fact that they published the specification for their library as an open standard that anyone could implement. As a result, the revised graphics library could be ported to other hardware and software platforms, and graphic hardware vendors could make their products compatible with this new graphic language just by writing device drivers to the open standard. The rewrite of IRIS GL and the new open specification were both dubbed OpenGL. To this day, OpenGL is the only truly cross-platform, language-agnostic, hardware accelerated graphics library in common use. Its primary competitor, DirectX, is only available on Microsoft's operating systems, including the various Windows and Windows Mobile operating systems, as well as the OS that runs the Xbox and XBox 360.

OpenGL prospered. It began to be used not just for 3D-modeling programs and scientific simulations, but as computers got more powerful, it began to be used for 3D games. SGI didn't do quite as well as the library it created. As computers became more capable, the need for expensive high-end workstations like the ones SGI manufactured began to dwindle, and SGI became a shadow of its former self, eventually declaring bankruptcy a few years ago. Although there is a company today called Silicon Graphics, it is not the same company that once dominated the graphic programming industry; SGI sold its name to another company as part of their bankruptcy proceedings.

The SGI Campus

At the height of success, SGI's headquarters were located in a beautiful multi-building complex in Mountain View, California,complete with swimming pool, forested walking trails, and 500,000 square feet of office space. The campus still exists today. Google uses it as their world headquarters and calls it the Googleplex.

However, the same increase in computing power that contributed to SGI's downfall was a boon for cross-platform OpenGL. Before long, hardware accelerated graphics were being used everywhere. Since OpenGL allows programmers targeting any operating system to take advantage of hardware acceleration without having to write to any specific hardware, a program written using OpenGL could get the benefit of hardware acceleration regardless of the operating system, graphic card, or processor that it was run on (assuming, of course, that the graphic card supports OpenGL). This took a lot of the stress and work out of graphics programming because it meant that programmers no longer had to write to specific hardware in order to get good performance; they could just write to the standard. Code that writes directly to hardware tends to break on hardware it wasn't written for, so games written using OpenGL tend to be less fragile than those written directly to hardware and have longer shelf-lives. Programs written using OpenGL are also far easier to port to other computing platforms because the datatypes and function calls are exactly the same no matter where your code runs. OpenGL allows developers to spend less time on performance optimization and more time on the specific coding tasks needed by their application.

The Rise of OpenGL ES

By 2006, it became apparent that the increases in computing power would soon lead to small, handheld devices capable of displaying 3D graphics. With an eye towards efficiency, a new graphics library was created by taking an older version of the OpenGL specification and stripping out some of the functionality that might have had a negative impact on performance. The team working on this new mobile graphics specification removed immediate mode (sometimes called direct mode, a slower, more tedious method of graphics programming that is mostly obsolete, but is still often used to teach graphics programming), double-precision floating point variables, and support for polygons other than triangles, to name just a few things. This new, leaner, more efficient graphic library was dubbed OpenGL for Embedded Systems, or just OpenGL ES.

OpenGL Basics

Because they had a very complex procedural library, SGI realized early on that they had to come up with some very distinct and consistent naming patterns to make their library usable and developer-friendly. Understanding the naming conventions used by the designers of OpenGL makes it easier to find what you're looking for and makes it easier to understand what existing code is doing. When you first look at OpenGL code, it can look like a jumble of nonsense. Once you understand its naming conventions, however, the odd names start to make an awful lot of sense. Let's let's look at these conventions quickly before we go any further.


One of the challenges facing the designers of OpenGL ES is that not all languages define exactly the same datatypes, and in some languages, like C, the exact size of those datatypes can vary. For example, the int datatype in C is based on the register size of the hardware being compiled for. That means an int can be compiled down to a different number of bytes on different hardware. To deal with this problem, OpenGL defines its own datatypes, all of which begin with the letters GL. When working in OpenGL ES, instead of using int, for example, you would probably choose to use GLint or maybe GLshort. While these are all integer datatypes and all work, the size of int is different on different platforms. The size of GLint and GLshort, however, are the same on every operating system, in every language, and on all hardware, giving more predictable results, avoiding runtime conversion, and generally making life much easier.

You should endeavor to use the OpenGL ES datatypes for any data that could potentially be submitted as part of an OpenGL ES library call, or that is retrieved from an OpenGL ES library call. For variables that will never interact with OpenGL, you do not have to use an OpenGL datatype and, in fact, you may find it makes your code clearer if you use the OpenGL datatypes only for values that will be used by OpenGL ES and use regular C datatypes like int or CoreFoundation datatypes like NSInteger everywhere else.

The OpenGL ES datatypes are as follows:

Type Bytes Min Max Comment
GLenum 4 0 4,294,967,295 For enumerated types
GLboolean 1 0 255 Boolean value, GL_TRUE or GL_FALSE
GLbitfield 4 - - Holds up to 32 Booleans, one per bit
GLbyte 1 -128 127
GLshort 2 -32,768 32,767
GLint 4 -2,147,483,648 2,147,483,647
GLsizei 4 -2,147,483,648 2,147,483,647 Similar to size_t
GLubyte 1 0 255
GLushort 2 0 65,535
GLuint 4 0 4,294,967,295
GLfloat 4 - - Floating point variable
GLclampf 4 - - Floating point between 0.0 and 1.0
GLvoid - - - No value
GLfixed 4 - - Fixed point numbers
GLclampx 4 - - Fixed-point between 0.0 and 1.0

Always make an effort to use the smallest datatype that suits your needs. For example, if you know you will never have more than a thousand vertices in a particular object, don't use a GLint to store the vertex indices for that object, since a GLshort is more than capable of holding all the numbers up to a thousand. There are some exceptions to this rule that we'll talk about later in the book, but it's a good general rule to keep in mind while you're programming. Remember, the iPhone has limited memory resources compared to your laptop or desktop computer, and those extra bytes can add up.

A few things to notice when looking at the table above: first, there is no GLdouble. OpenGL ES 2.0 doesn't have double-precision floating point variables. These were intentionally left out due to OpenGL ES's emphasis on performance and resource utilization. The authors of the OpenGL ES specification felt that the screen sizes of embedded devices would not benefit from the use of double-precision (64-bit) floating point values, and that their use could very quickly eat up the limited system resources available on a these device. Second, the last two datatypes on the list, GLfixed and GLclampx, are used to represent floating point values using integer datatypes using something called fixed point representation.

This is a common optimization on systems where floating point math performance is considerably slower than integer math performance. Since all iPhones, iPod touches, and the iPad have GPUs that work natively with floating point numbers and are capable of doing very fast floating point operations, we won't be discussing the use of fixed point mathematics in this book at all.

OpenGL Versions

Both OpenGL and OpenGL ES come in several different versions. With workstation OpenGL, each newly released version tends to build on the old, but have historically remained almost completely backward-compatible with earlier versions. So, for example, most OpenGL 1.5 code will compile and run just fine using OpenGL 3.0. Versions 3.1, 3.2, and 4.0 have begun to remove Direct mode and a few other legacy features, but still maintain an incredible amount of backward compatibility with previous versions.

In OpenGL ES, this is not the case at all. Keeping with OpenGL ES's focus on performance, new versions of OpenGL ES often drop support for features used in the previous version, especially if a faster or more efficient way of accomplishing that task has been added to the later version. In order to take full advantage of today's iOS hardware, you have to use OpenGL ES 2.0. Even though current devices do support OpenGL ES 1.1, you can't intermix 1.1 and 2.0 code. As a result, this book deals exclusively with OpenGL ES 2.0.

Alphabet Soup: OpenGL Functions

Because you're working with a procedural library, everything you do in OpenGL ES is accomplished by making standard C function calls, and all OpenGL ES functions begin with the letters gl. Many OpenGL ES functions also have a string of characters at the end that tell you a little bit about the parameters the function expects you to pass in. OpenGL ES functions that don't take any parameters don't have these letters at the end, for example:

glEnum error = glGetError();

This function is used to find out if any errors occurred in the previous OpenGL ES function call. Most OpenGL ES functions do not return an error code themselves, so you have to call this function separately if you want to find out if everything worked okay.
If a function does take one or more arguments, but it always and only takes one specific type of argument, then the function also generally will not have have characters at the end, such as:

glUseProgram(program);

The function above, glUseProgram(), always and only takes a GLuint, and there are no other versions of this particular function. As a result, there's no alphabet soup at the end of the function name because there's no need for it. You have no choice in the datatype to pass in. Don't worry too much about what this function actually does for now; you'll start using it in a few chapters and will be very well acquainted with it by the end of the book.

Name by Any Other Name.

OpenGL has some terminology that can be downright confusing at times. OpenGL often takes words and gives them a very specific meaning that's different from ordinary English usage. Sometimes that usage is so different that the use of the term seems at odds with the plain-English meaning of the word. One example of this is the word name. In OpenGL parlance, a name is a number, usually a GLint or GLuint. In OpenGL ES, names are never a string and never have any semantic meaning. What OpenGL means by name is simply a unique identifier for a specific object. The reason for this is that integers are far more efficient to use to uniquely identify an object; strings comparisons, on the other hand, are costly.

When you ask OpenGL to create an object¹ for you using a function, it will assign a unique integer value to identify the object it creates and it will return that unique integer to the calling code. When you later want to do something with a specific object, you pass the object's name (the number returned by the earlier call) to OpenGL as one of the arguments to the function call in order to tell OpenGL ES which object the function call should act upon.

A great many OpenGL function calls can be made using more than one datatype, however. OpenGL ES gives you this flexibility so you can choose the most efficient data type for your needs. For these functions, The first and sometimes the second letter after the name of the function specify the datatype that this function expects. Here's what each suffix refers to:

Letter(s)

Datatype Taken

b GLbyte
s GLshort
i GLint
f GLfloat
ub GLubyte
us GLushort
ui GLuint

So a function called glFoof() is glFoo that expects a GLfloat to be passed in as an argument, while glFoos() is the same function, but expects a GLshort. Since the name of the two functions is identical except for the letters at the end, you know that both function calls accomplish exactly the same task.

Sometimes, there is also a number as part of the function suffix. This number represents the number of values that the function expects. So, for example, the function glFoo1f() would expect a single GLfloats, while glFoo2f() would expect two GLfloats. Both methods would accomplish exactly the same task, however.

Some OpenGL ES functions serve a very generic purpose and need to be able to take a variable number of arguments or else need you to pass a value by reference so the function can change the original value. All OpenGL ES functions we've seen so far expect a single value per argument and those values are passed by value. This means the called function gets its own copy of each value, the number of arguments cannot change, and any changes made to those values by the called method do not impact the calling code. When OpenGL needs you to pass a pointer in as an argument, either because it needs to be able to change the value, or because it wants you to be able to pass in an indefinite number of arguments, it will add a v suffix at the end of the alphabet soup. For example, the function glFoo1fv() would expect you to pass in a single pointer to a GLfloat.

Let's Begin

At this point, you should have a basic understanding of the where OpenGL ES came from and why it was designed the way it was. The next thing you need to understand before actually coding in OpenGL is some of the fundamental math underlying graphics programming. Don't worry, we'll start simple and ease into the more complex math so it won't hurt too badly.


1 - Intermixing a functional library with an object-oriented language can sometimes lead to confusions. OpenGL ES has things it calls "objects", but they are not object as far as Objective-C is concerned. Usually, which type of object I'm referring to will be clear from the context, but when it's not, I'll try to identify whether “object” refers to an Objective-C object or an OpenGL object.

No comments:

Post a Comment