【转载】 Moving Beyond OpenGL 1.1 for Windows

原文网址:http://www.gamedev.net/reference/articles/article1929.asp

 

 

Moving Beyond OpenGL1.1 for Windows
Trick #10 from Game Programming Tricks of the Trade, Premier Press
by Dave Astle

Author's note: Keep in mind that although this article is being published on GameDev.net in April 2003, it was written in April 2002, and some things have changed in the past year, notably the release of the OpenGL 1.4 specification, and the standardization of pixel and vertex shaders. I've chosen not to update the article to reflect these changes because I wanted to keep the text consistent with what was published in the book, and because they really make no difference as far as the the purpose of the article is concerned.

Introduction

Once you've been programming with OpenGL for Windows for a while, you'll probably notice something: the headers and libraries you're using are old. Dig around in the gl.h header, and you'll see this:

#define GL_VERSION_1_1                    1


This article originally appeared in the book Game Programming Tricks of the Trade, 2002, Premier Press. Many members of the GameDev.net community, including several GDNet staff members, contributed to the book, so you're encouraged to check it out.
This means that you're using OpenGL 1.1, which was released in 1996. In the world of graphics, that's ancient! If you've been paying attention, you know that the current OpenGL specification is at 1.3 (at least at the time of this writing). OpenGL 1.4 should be released later this year, with 2.0 following soon after. Obviously, you need to update your OpenGL headers and libraries to something more recent.

As it turns out, the most recent headers and libraries for Windows correspond to � OpenGL 1.1. That's right, the files you already have are the most recent ones available.

This, of course, presents a problem. Although you can do some impressive things with OpenGL 1.1, to take full advantage of modern consumer graphics hardware, you're going to need functionality available through more recent versions, as well as features available through extensions (but we'll get to that in a bit). The question, then, is how to access newer features, when your headers and libraries are stuck at OpenGL 1.1. The purpose of this article is to answer that question.

What You Will Learn

In this article, I will:

  • Explain in greater detail why you need to take some extra steps to use anything beyond OpenGL 1.1.
  • Explain OpenGL's extension mechanism, and how it can be used to access OpenGL 1.2 and 1.3 functionality.
  • Give you an overview of the new options available in OpenGL 1.2 and 1.3, as well as a look at some of the most useful extensions.
  • Give you some tips for using extensions while ensuring that your game will run well on a wide range of systems.
  • Provide a demo showing you how to use the techniques described.

The Problem

If you're new to OpenGL or have only ever needed the functionality offered in OpenGL 1.1, you may be confused about what the problem is, so let's clarify.

To develop for a given version of OpenGL on Windows, you need three things. First, you need a set of libraries (i.e. opengl32.lib and possibly others such as glu32.lib) and headers (i.e. gl.h, and so on) corresponding to the version you'd like to use. These headers and libraries contain the OpenGL functions, constants, and other things you need to be able to compile and link an OpenGL application. Second, the system you intend to run the application on needs to have an OpenGL dynamic link library (OpenGL32.dll), or OpenGL runtime library. The runtime needs to be for either the same or a more recent version of OpenGL as the headers and libraries you're using. Ideally, you will also have a third component, called an Installable Client Driver (IDC). An IDC is provided by the video card drivers to allow for hardware acceleration of OpenGL features, as well as possible enhancements provided by the graphics vendor.

So, let's look at these three things and see why you have to jump through a few hoops to use anything newer than OpenGL 1.1:

  • Headers and libraries. As I mentioned in the introduction, the latest version of the OpenGL headers and libraries available from Microsoft correspond to version 1.1. If you look around on the Internet, you may come across another OpenGL implementation for Windows created by Silicon Graphics. SGI's implementation also corresponds to OpenGL 1.1. Unfortunately, this implementation is no longer supported by SGI. In addition, the Microsoft implementation is based upon it, so you really gain nothing by using it. Where does that leave us?

    Well, there is reason to hope that someone will release up to date libraries. Although, to my knowledge, no one has committed to doing so, several parties have discussed it. Microsoft is the obvious candidate, and despite years of promising and not delivering, it appears that they have taken an interest in the recently proposed OpenGL 2.0. Whether or not that interest will lead to action remains to be seen, but given the large number of graphics workstations running Windows NT and Windows 2000, it's not beyond the realm of possibility.

    Besides Microsoft, there has apparently been discussion among the members of OpenGL's Architectural Review Board (ARB) to provide their own implementation of the headers and libraries. At present, though, this is still in the discussion stage, so it may be a while before we see anything come of it.

  • The runtime. Most versions of Windows (the first release of Windows 95 being the exception) come with a 1.1 runtime. Fortunately, this isn't really as important as the other elements. All that the runtime does is guarantee a baseline level of functionality, and allow you to interface with the ICD.

  • The ICD. This is the one area where you're okay. Most hardware vendors (including NVIDIA and ATI) have been keeping up with the latest OpenGL standard. For them to be able to advertise that their drivers are compliant with the OpenGL 1.3 standard, they have to support everything included in the 1.3 specification (though not necessarily in hardware). The cool thing about this is that the ICD contains the code to do everything in newer versions of OpenGL, and we can take advantage of that.

The thing that's important to note here is that although the headers and libraries available don't directly allow you to access newer OpenGL features, the features do exist in the video card drivers. You just need to find a way to access those features in our code. We do that by using OpenGL's extension mechanism.

OpenGL Extensions

As you're aware, the graphics industry has been moving at an alarmingly rapid pace for many years now. Today, consumer-level video cards include features that were only available on professional video cards (costing thousands of dollars) a few years ago. Any viable graphics API has to take these advances into account, and provide some means to keep up with them. OpenGL does this through extensions.

If a graphics vendor adds a new hardware feature that they want OpenGL programmers to be able to take advantage of, they simply need to add support for it in their ICD, and then provide developers with documentation about how to use the extension. This is oversimplifying a bit, but it's close enough for our purposes. As an OpenGL programmer, you can then access the extension through a common interface shared by all extensions. You'll learn how to do that in the "Using Extensions" section, but for now, let's look at how extensions are identified, and what they consist of.

Extension Names

Every OpenGL extension has a name by which it can be precisely and uniquely identified. This is important, because hardware vendors will frequently introduce extensions with similar functionality but very different semantics and usage. You need to be able to distinguish between them. For example, both NVIDIA and ATI provide extensions for programmable vertex and pixel shaders, but they bear little resemblance to each other. So, if you wanted to use pixel shaders in your program, it wouldn't be enough to find out if the hardware supported pixel shaders. You'd have to be able to specifically ask whether NVIDIA's or ATI's version is supported, and handle each appropriately.

All OpenGL extensions use the following naming convention:

PREFIX_extension_name

The "PREFIX" is there to help avoid naming conflicts. It also helps identify the developer of the extension or, as in the case of EXT and ARB, its level of promotion. Table 1 lists most of the prefixes currently in use. The "extension_name" identifies the extension. Note that the name cannot contain any spaces. Some example extension names are ARB_multitexture, EXT_bgra, NV_vertex_program, and ATI_fragment_shader.

Table 1 - OpenGL Extension Prefixes
Prefix    Meaning/Vendor
ARB Extension approved by OpenGL's Architectural Review Board (first introduced with OpenGL 1.2)
EXT Extension agreed upon by more than one OpenGL vendor
3DFX 3dfx Interactive
APPLE Apple Computer
ATI ATI Technologies
ATIX ATI Technologies (experimental)
HP Hewlett-Packard
INTEL Intel Corporation
IBM International Business Machines
KTX Kinetix
NV NVIDIA Corporation
MESA http://www.mesa3d.org
OML OpenML
SGI Silicon Graphics
SGIS Silicon Graphics (specialized)
SGIX Silicon Graphics (experimental)
SUN Sun Microsystems
SUNX Sun Microsystems (experimental)
WIN Microsoft

Caution: Some extensions share a name, but have a different prefix. These extensions are generally not interchangeable, as they may use entirely different semantics. For example, ARB_texture_env_combine is not the same thing as EXT_texture_env_combine. Rather than making assumptions, be sure to consult the extension specifications when you're unsure.

What an Extension Includes

You now know what an extension is, and how extensions are named. Next, let's turn our attention to the relevant components of an extension. There are four parts of an extension that you need to deal with.

Name Strings
Each extension defines a name string, which you can use to determine whether or not the OpenGL implementation supports it. By passing GL_EXTENSIONS to the glGetString() method, you can get a space-delimited buffer containing all the extension name strings supported by the implementation.

Name strings are generally the name of the extension preceded by another prefix. For core OpenGL name strings, this is always GL_ (e.g. GL_EXT_texture_compression). When the name string is tied to a particular windows system, the prefix will reflect which system that is (e.g. Win32 uses WGL_).

Some extensions may define more than one name string. This would be the case if the extension provided both core OpenGL functionality and functionality specific to the windows system.

Functions
Many (but not all) extensions introduce one or more new functions to OpenGL. To use these functions, you'll have to obtain their entry point, which requires that you know the name of the function. This process is described in detail in the "Using Extensions" section.

The functions defined by the extension follow the naming convention used by the rest of OpenGL, namely glFunctionName(), with the addition of a suffix using the same letters as the extension name's prefix. For example, the NV_fence extension includes the functions glGetFencesNV(), glSetFenceNV(), glTestFenceNV(), and so on.

Enumerants
An extension may define one or more enumerants. In some extensions, these enumerants are intended for use in the new functions defined by the extension (which may be able to use existing enumerants as well). In other cases, they are intended for use in standard OpenGL functions, thereby adding new options to them. For example, the ARB_texture_env_add extension defines a new enumerant, GL_ADD. This enumerant can be passed as the params parameter of the various glTexEnv() functions when the pname parameter is GL_TEXTURE_ENV_MODE.

The new enumerants follow the normal OpenGL naming convention (i.e. GL_WHATEVER), except that they are suffixed by the letters used in the extension name's prefix, such as GL_VERTEX_SOURCE_ATI.

Using new enumerants is much simpler than using new functions. Usually, you will just need to include a header defining the enumerant, which you can get from your hardware vendor or from SGI. Alternately, you can define the enumerant yourself if you know the integer value it uses. This value can be obtained from the extension's documentation.

Extensions don't need to define both functions and enumerants (though many do), but they usually include at least one of the two. The few cases where that's not true is when existing functions or enumerants are combined in new ways.

Dependencies
Very few extensions stand completely alone. Some require the presence of other extensions, while others take this a step further and modify or extend the usage of other extensions. When you begin using a new extension, you need to be sure to read the specification and understand the extension's dependencies.

Speaking of documentation, you're probably wondering where you can get it, so let's talk about that next.

Extension Documentation

Although vendors may (and usually do) provide documentation for their extensions in many forms, there is one piece of documentation that is absolutely essential-- the specification. These are generally written as plain text files, and include a broad range of information about the extension, such as its name, version, number, dependencies, new functions and enumerants, issues, and modifications/additions to the OpenGL specification.

The specifications are intended for use by developers of OpenGL hardware or ICDs, and as such, are of limited use to game developers. They'll tell you what the extension does, but not why you'd want to use it, or how to use it. For that reason, I'm not going to go over the details of the specification format. If you're interested, Mark Kilgard has written an excellent article about it which you can read at OpenGL.org. [1]

As new extensions are released, their specifications are listed in the OpenGL Extension Registry, which you can find at the following URL:

http://oss.sgi.com/projects/ogl-sample/registry/

This registry is updated regularly, so it's a great way to keep up with the newest additions to OpenGL.

For more detailed descriptions of new extensions, your best bet is the websites of the leading hardware vendors. In particular, NVIDIA [2] and ATI [3] both provide a wealth of information, including white papers, Power Point presentations, and demos.

Extensions that are promoted to be a part of the core OpenGL specification may be removed from the extension registry. To obtain information about these, you'll have to refer to the latest OpenGL specification. [4]

Using Extensions

Finally, it's time to learn what you need to do to use an extension. In general, there are only a couple of steps you need to take:

  • determine whether or not the extension is supported
  • obtain the entry point for the any of the extension's functions that you want to use
  • define any enumerants you're going to use.

Let's look at each of these steps in greater detail.

Caution: Before checking for extension availability and obtaining pointers to functions, you MUST have a current rendering context. In addition, the entry points are specific to each rendering context, so if you're using more than one, you'll have to obtain a separate entry point for each.

Querying the Name String

In order to find out whether or not a specific extension is available, first get the list of all the name strings supported by the OpenGL implementation. To do this, you just need to call glGetString() using GL_EXTENSIONS, like so:

char* extensionsList = (char*) glGetString(GL_EXTENSIONS);

After this executes, extensionsList points to a null-terminated buffer containing the name strings of all the extensions available to you. These name strings are separated by spaces, including a space after the last name string.

I'm casting the value returned by glGetString() because the function actually returns an array of unsigned chars. Since most of the string manipulation functions I'll be using require signed chars, I do the cast once now instead of doing it many times later.

To find out whether or not the extension you're looking for is supported, you'll need to search this buffer to see if it includes the extension's name string. I'm not going to go into great detail about how to parse the buffer, since there are many ways to do so, and it's something that at this stage in your programming career, you should be able to do without much effort. One thing you need to watch out for, though, is accidentally matching a substring. For example, if you're trying to use the EXT_texture_env extension, and the implementation doesn't support it, but it does support EXT_texture_env_dot3, then calling something like:

strstr("GL_EXT_texture_env", extensionsList);

is going to give you positive results, making you think that the EXT_texture_env extension is supported, when it's really not. The CheckExtension() function in the demo program included with this article shows one way to avoid this problem.

Obtaining the Function's Entry Point

Because of the way Microsoft handles its OpenGL implementation, calling a new function provided by an extension requires that you request a function pointer to the entry point from the ICD. This isn't as bad as it sounds.

First of all, you need to declare a function pointer. If you've worked with function pointers before, you know that they can be pretty ugly. If not, here's an example:

void (APIENTRY * pglCopyTexSubImage3DEXT) (GLenum, GLint, GLint,
GLint, GLint, GLint, GLint, GLsizei, GLsizei) = NULL;
Update 4/24/03: For the book, and initially here, I used the function name (i.e. glCopyTexSubImage3DEXT) as the pointer name. A reader pointed out to me that on a number of operating systems (e.g. Linux) this can cause serious problems, so it should be avoided. Thanks, Ian!

Now that we have the function pointer, we can attempt to assign an entry point to it. This is done using the function wglGetProcAddress():

PROC wglGetProcAddress( LPCSTR  lpszProcName );

The only parameter is the name of the function you want to get the address of. The return value is the entry point of the function if it exists, or NULL otherwise. Since the value returned is essentially a generic pointer, you need to cast it to the appropriate function pointer type.

Let's look at an example, using the function pointer we declared above:

pglCopyTexSubImage3DEXT  =
(void (APIENTRY *) (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei))
wglGetProcAddress("glCopyTexSubImage3DEXT");

And you thought the function pointer declaration was ugly.

You can make life easier on yourself by using typedefs. In fact, you can obtain a header called "glext.h" which contains typedefs for most of the extensions out there. This header can usually be obtained from your favorite hardware vendor (for example, NVIDIA includes it in their OpenGL SDK), or from SGI at the following URL:

http://oss.sgi.com/projects/ogl-sample/ABI/glext.h

Using this header, the code above becomes:

PFNGLCOPYTEXSUBIMAGE3DEXTPROC pglCopyTexSubImage3DEXT = NULL;
pglCopyTexSubImage3DEXT = (PFNGLCOPYTEXSUBIMAGE3DEXTPROC) wglGetProcAddress("glCopyTexSubImage3DEXT");

Isn't that a lot better?

As long as wglGetProcAddress() doesn't return NULL, you can then freely use the function pointer as if it were a normal OpenGL function.

Declaring Enumerants

To use new enumerants defined by an extension, all you have to do is define the enumerant to be the appropriate integer value. You can find this value in the extension specification. For example, the specification for the EXT_texture_lod_bias says that GL_TEXTURE_LOD_BIAS_EXT should have a value of 0x8501, so somewhere, probably in a header (or possibly even in gl.h), you'd have the following:

#define GL_TEXTURE_LOD_BIAS_EXT    0x8501

Rather than defining all these values yourself, you can use the glext.h header, mentioned in the last section, since it contains all of them for you. Most OpenGL programmers I know use this header, so don't hesitate to use it yourself and save some typing time.

Win32 Specifics

In addition to the standard extensions that have been covered so far, there are some extensions that are specific to the Windows system. These extensions provide additions that are very specific to the windowing system and the way it interacts with OpenGL, such as additional options related to pixel formats. These extensions are easily identified by their use of "WGL" instead of "GL" in their names. The name strings for these extensions normally aren't included in the buffer returned by glGetString(GL_EXTENSIONS), although a few are. To get all of the Windows-specific extensions, you'll have to use another function, wglGetExtensionsStringARB(). As the ARB suffix indicates, it's an extension itself (ARB_extensions_string), so you'll have to get the address of it yourself using wglGetProcAddress(). Note that for some reason, some ICDs identify this as wglGetExtensionsStringEXT() instead, so if you fail to get a pointer to one, try the other. The format of this function is as follows:

const char* wglGetExtensionsStringARB(HDC hdc);
Caution: Normally, it's good practice to check for an extension by examining the buffer returned by glGetString() before trying to obtain function entry points. However, it's not strictly necessary to do so. If you try to get the entry point for a non-existant function, wglGetProcAddress() will return NULL, and you can simply test for that. The reason I'm mentioning this is because to use wglGetExtensionsStringARB(), that's exactly what you have to do. It appears that with most ICDs, the name string for this extension, WGL_ARB_extensions_string, doesn't appear in the buffer returned by glGetString(). Instead, it is included in the buffer returned by wglGetExtensionsStringARB()! Go figure.

Its sole parameter is the handle to your rendering context. The function returns a buffer similar to that returned by glGetString(GL_EXTENSIONS), with the only difference being that it only contains the names of WGL extensions.

Some WGL extension string names included in the buffer returned by wglGetExtensionsStringARB() may also appear in the buffer returned by glGetString(). This is due to the fact that those extensions existed before the creation of the ARB_extensions_string extension, and so their name strings appear in both places to avoid breaking existing software.

Just as there is a glext.h header for core OpenGL extensions, so is there a wglext.h for WGL extensions. You can find it at the following link:

http://oss.sgi.com/projects/ogl-sample/ABI/wglext.h

Extensions and OpenGL 1.2 and 1.3, and the Future

Back at the beginning of this article, I said that OpenGL 1.2 and 1.3 features can be accessed using the extensions mechanism, which I've spent the last several pages explaining. The question, then, is how you go about doing that. The answer, as you may have guessed, is to treat 1.2 and 1.3 features as extensions. When it comes right down to it, that's really what they are, since nearly every feature that has been added to OpenGL originated as an extension. The only real difference between 1.2 and 1.3 features and "normal" extensions is that the former tend to be more widely supported in hardware, because, after all, they are part of the standard.

Sometimes, an extension that has been added to the OpenGL 1.2 or 1.3 core specification will undergo slight changes, causing the semantics and/or behavior to be somewhat different from what is documented in the extension's specification. You should check the latest OpenGL specification to find out about these changes.

The next update to OpenGL will probably be 1.4. It will most likely continue the trend of promoting successful extensions to become part of the standard, and you should be able to continue to use the extension mechanism to access those features. After that, OpenGL 2.0 will hopefully make its appearance, introducing some radical changes to the standard. Once 2.0 is released, new headers and libraries may be released as well, possibly provided by the ARB members. These will make it easier to use new features.

What You Get

As you can see, using OpenGL 1.2 and 1.3, and extensions in general, isn't a terribly difficult process, but it does take some extra effort. You may be wondering what you can gain by using them, so lets take a closer look at them. The following sections list the features added by OpenGL 1.2 and 1.3, as well as some of the more useful extensions currently available. With each feature, I've included the extension you can use to access it.

OpenGL 1.2

3D Textures allow you to do some really cool volumetric effects. Unfortunately, they require a significant amount of memory. To give you an idea, a single 256x256x256 16 bit texture will use 32 MB! For this reason, hardware support for them is relatively limited, and because they are also slower than 2D textures, they may not always provide the best solution. They can, however, be useful if used judiciously. 3D textures correspond to the EXT_texture3D extension.

BGRA Pixel Formats make it easier to work with file formats which use blue-green-red color component ordering rather than red-green-blue. Bitmaps and Targas are two examples that fall in this category. BGRA pixel formats correspond to the EXT_bgra extension.

Packed Pixel Formats provide support for packed pixels in host memory, allowing you to completely represent a pixel using a single unsigned byte, short, or int. Packet pixel formats correspond to the EXT_packed_pixels extension, with some additions for reversed component order.

Normally, since texture mapping happens after lighting, modulating a texture with a lit surface will "wash out" specular highlights. To help avoid this affect, the Separate Specular Color feature has been added. This causes OpenGL to track the specular color separately and apply it after texture mapping. Separate specular color corresponds to the EXT_separate_specular_color extension.

Texture Coordinate Edge Clamping addresses a problem with filtering at the edges of textures. When you select GL_CLAMP as your texture wrap mode and use a linear filtering mode, the border will get sampled along with edge texels. Texture coordinate edge clamping causes only the texels which are actually part of the texture to be sampled. This corresponds to the SGIS_texture_edge_clamp extension (which normally shows up as EXT_texture_edge_clamp in the GL_EXTENSIONS string).

Normal Rescaling allows you to automatically scale normals by a value you specify, which can be faster than renormalization in some cases, although it requires uniform scaling to be useful. This corresponds to the EXT_rescale_normal extension.

Texture LOD Control allows you to specify certain parameters related to the texture level of detail used in mipmapping to avoid popping in certain situations. It can also be used to increase texture transfer performance, since the extension can be used to upload only the mipmap levels visible in the current frame, instead of uploading the entire mipmap hierarchy. This matches the SGIS_texture_lod extension.

The Draw Element Range feature adds a new function to be used with vertex arrays. glDrawRangeElements() is similar to glDrawElements(), but it lets you indicate the range of indicies within the arrays that you are using, allowing the hardware to process the data more efficiently. This corresponds to the EXT_draw_range_elements extension.

The Imaging Subset is not fully present in all OpenGL implementations, since it's primarily intended for image processing applications. It's actually a collection of several extensions. The following are the ones that may be of interest to game developers.

  • EXT_blend_color allows you to specify a constant color which is used to define blend weighting factors.
  • SGI_color_matrix introduces a new matrix stack to the pixel pipeline, causing the RGBA components of each pixel to be multiplied by a 4x4 matrix.
  • EXT_blend_subtract gives you two ways to use the difference between two blended surfaces (rather than the sum).
  • EXT_blend_minmax lets you keep either the minimum or maximum color components of the source and destination colors.

OpenGL 1.3

The Multitexturing extension was promoted to ARB status with OpenGL 1.2.1 (the only real change in that release), and in 1.3, it was made part of the standard. Multitexturing allows you to apply more than one texture to a surface in a single pass, which is useful in many things, such as lightmapping and detail texturing. It was promoted from the ARB_multitexture extension.

Texture Compression allows you to either provide OpenGL with precompressed data for your textures, or to have the driver compress the data for you. The advantage in doing so is that you save both texture memory and bandwidth, thereby improving performance. Compressed textures were promoted from the ARB_compressed_textures extension.

Cube Map Textures provide a new type of texture consisting of six two-dimensional textures in the shape of a cube. Texture coordinates act like a vector from the center of the cube, indicating which face and which texels to use. Cube mapping is useful in environment mapping and texture-based diffuse lighting. It is also important for pixel-perfect dot3 bumpmapping, as a normalization lookup for interpolated fragment normals. It was promoted from the ARB_texture_cube_map extension.

Multisampling allows for automatic antialiasing by sampling all geometry several times for each pixel. When it's supported, and extra buffer is created which contains color, depth, and stencil values. Multisampling is, of course, expensive, and you need to be sure to request a rendering context that supports it. It was promoted from the ARB_multisampling extension.

The Texture Add Environment Mode adds a new enumerant which can be passed to glTexEnv(). It causes the texture to be additively combined with the incoming fragment. This was promoted from the ARB_texture_env_add extension.

Texture Combine Environment Modes add a lot of new options for the way textures are combined. In addition to the texture color and the incoming fragment, you can also include a constant texture color and the results of the previous texture environment stage as parameters. These parameters can be combined using passthrough, multiplication, addition, biased addition, subtraction, and linear interpolation. You can select combiner operations for the RGB and alpha components separately. You can also scale the final result. As you can see, this addition gives you a great deal of flexibility. Texture combine environment modes were promoted from the ARB_texture_env_combine extension.

The Texture Dot3 Environment Mode adds a new enumerant to the texture combine environment modes. The dot3 environment mode allows you to take the dot product of two specified components and place the results in the RGB or RGBA components of the output color. This can be used for per-pixel lighting or bump mapping. The dot3 environment mode was promoted from the ARB_texture_env_dot3 extension.

Texture Border Clamp is similar to texture edge clamp, except that it causes texture coordinates that straddle the edge to sample from border texels only, rather than from edge texels. This was promoted from the ARB_texture_border_clamp extension.

Transpose Matrices allow you to pass row major matrices to OpenGL, which normally uses column major matrices. This is useful not only because it is how C stores two dimensional arrays, but because it is how Direct3D stores matricies, which saves conversion work when you're writing a rendering engine that uses both APIs. This addition only adds to the interface; it does not change the way OpenGL works internally. Transpose matrices were promoted from the ARB_transpose_matrix extension.

Useful Extensions

At the time of writing, there are 269 extensions listed in the Extension Registry. Even if I focused on the ones actually being used, I couldn't hope to cover them all, even briefly. Instead, I'll focus on a few that seem to be the most important for use in games.

Programmable Vertex and Pixel Shaders
It's generally agreed that shaders are the future of graphics, so let's start with them. First of all, the terms "vertex shader" and "pixel shader" are in common usage because of the attention they received with the launch of DirectX 8. However, the OpenGL extensions that you use for them have different names. On NVIDIA cards, vertex shaders are called vertex programs, which are available through the NV_vertex_program extension. Pixel shaders are called register combiners, and are available through the NV_register_combiners and NV_texture_shader extensions. On ATI cards, vertex shaders are still called vertex shaders, and are available through the EXT_vertex_shader extension. Pixel shaders are called fragment shaders, and are available through the ATI_fragment_shader extension.

If you're unfamiliar with shaders, then a quick overview is in order. Vertex shaders allow you to customize the geometry transformation pipeline. Pixel shaders work later in the pipeline, and allow you to control how the final pixel color is determined. Together, the two provide incredible functionality. I recommend that you download NVIDIA's Effects Browser to see examples of the things you can do with shaders.

Using shaders can be somewhat problematic right now due to the fact that NVIDIA and ATI both handle them very differently. If you want your game to take advantage of shaders, you'll have to write a lot of special case code to use each vendor's method. At the ARB's last several meetings, this has been a major discussion point. There is a great deal of pressure to create a common shader interface. In fact, it is at the core of 3D Labs' OpenGL 2.0 proposal. Hopefully, the 1.4 specification will address this issue, but the ARB seems to be split as to whether a common shader interface should be a necessary component of 1.4.

Compiled Vertex Arrays
The EXT_compiled_vertex_arrays extension adds two functions which allow you to lock and unlock your vertex arrays. When the vertex arrays are locked, OpenGL assumes that their contents will not be changed. This allows OpenGL to make certain optimizations, such as caching the results of vertex transformation. This is especially useful if your data contains large numbers of shared vertices, or if you are using multipass rendering. When a vertex needs to be transformed, the cache is checked to see if the results of the transformation are already available. If they are, the cached results are used instead of recalculating the transformation.

The benefits gained by using CVAs depend on the data set, the video card, and the drivers. Although you generally won't see a decrease in performance when using CVAs, it's quite possible that you won't see much of an increase either. In any case, the fact that they are fairly widely supported makes them worth looking into.

WGL Extensions
There are a number of extensions available that add to the way Windows interfaces with OpenGL. Here are some of the main ones.

  • ARB_pixel_format augments the standard pixel format functions (i.e. DescribePixelFormat, ChoosePixelFormat, SetPixelFormat, and GetPixelFormat), giving you more control over which pixel format is used. The functions allow you to query individual pixel format attributes, and allow for the addition of new attributes that are not included in the pixel format descriptor structure. Many other WGL extensions are dependent on this extension.
  • ARB_pbuffer adds pixel buffers, which are off-screen (non-visible) rendering buffers. On most cards, these buffers are in video memory, and the operation is hardware accelerated. They are often useful for creating dynamic textures, especially when used with the render texture extension.
  • ARB_render_texture depends on the pbuffer extension. It is specifically designed to provide buffers that can be rendered to and be used as texture data. These buffers are the perfect solution for dynamic texturing.
  • ARB_buffer_region allows you to save portions of the color, depth, or stencil buffers to either system or video memory. This region can then be quickly restored to the OpenGL window.

Fences and Ranges
NVIDIA has created two extensions, NV_fence and NV_vertex_array_range, that can make video cards based on NVIDIA chipsets use vertex data much more efficiently than they normally would.

On NVIDIA hardware, the vertex array range extension is currently the fastest way to transfer data from the application to the GPU. Its speed comes from the fact that it allows the developer to allocate and access memory that normally can only be accessed by the GPU.

Although not directly related to the vertex array range extension, the fence extension can help make it even more efficient. When a fence is added to the OpenGL command stream, it can then be queried at any time. Usually, it is queried to determine whether or not it has been completed yet. In addition, you can force the application to wait for the fence to be completed. Fences can be used with vertex array range when there is not enough memory to hold all of your vertex data at once. In this situation, you can fill up available memory, insert a fence, and when the fence has completed, repeat the process.

Shadows
There are two extensions, SGIX_shadow and SGIX_depth_texture, which work together to allow for hardware-accelerated shadow mapping techniques. The main reason I mention these is that there are currently proposals in place to promote these extensions to ARB status. In addition, NVIDIA is recommending that they be included in the OpenGL 1.4 core specification. Because they may change somewhat if they are promoted, I won't go into detail about how these extensions work. They may prove to be a very attractive alternative to the stencil shadow techniques presently in use.

Writing Well-Behaved Programs Using Extensions

Something you need to be very aware of when using any extension is that it is highly likely that someone will run your program on a system that does not support that extension. It's your responsibility to make sure that when this happens, your program behaves intelligently, rather than crashing or rendering garbage to the screen. In this section, you'll learn several methods to help ensure that your program gets the best possible results on all systems. The focus is on two areas: how to select which extensions to use, and how to respond when an extension you're using isn't supported.

Choosing Extensions

The most important thing you can do to insure that your program runs on as many systems as possible is to choose your extensions wisely. The following are some factors you should consider.

Do you really need the extension?
A quick look at the Extension Registry will reveal that there are a lot of different extensions available, and new ones are being introduced on a regular basis. It's tempting to try many of them out just to see what they do. If you're coding a demo, there's nothing wrong with this, but if you're creating a game that will be distributed to a lot of people, you need to ask yourself whether or not the extension is really needed. Does it make your game run faster? Does it make your game use less video memory? Does it improve the visual quality of your game? Will using it reduce your development time? If the answer to any of these is yes, then the extension is probably a good candidate for inclusion in your product. On the other hand, if it offers no significant benefit, you may want to avoid it altogether.

What level of promotion is the extension at?
Extensions with higher promotion levels tend to be more widely supported. Any former extension that has been made part of the core 1.2 or 1.3 specification will be supported in compliant implementations, so they are the safest to use (1.2 more so than 1.3 since it's been around for longer). ARB-approved extensions (the ones that use the ARB prefix) aren't required to be supported in compliant implementations, but they are expected to be widely supported, so they're the next safest. Extensions using the EXT prefix are supported by two or more hardware vendors, and are thus moderately safe to use. Finally, vendor specific extensions are the most dangerous. Using them generally requires that you write a lot of special case code. However, they often offer significant benefits, so they should not be ignored. You just have to be especially careful when using them.

There are times when a vendor-specific extension can be completely replaced by an EXT or ARB extension. In this case, the latter should always be favored.

Who is your target audience?
If your target audience is hardcore gamers, you can expect that they are going to have newer hardware that will support many, if not all, of the latest extensions, so you can feel safer using them. Moreover, they will probably expect you to use the latest extensions; they want your game to take advantage of all those features they paid so much money for!

If, on the other hand, you're targeting casual game players, you'll probably want to use very few extensions, if any.

When will your game be done?
As mentioned earlier, the graphics industry moves at an extremely quick pace. An extension that is only supported on cutting-edge cards today may enjoy widespread support in two years. Then again, it may become entirely obsolete, either because it is something that consumers don't want, or because it gets replaced by another extension. If your ship date is far enough in the future, you may be able to risk using brand new extensions to enhance your game's graphics. On the other hand, if your game is close to shipping, or if you don't want to risk possible rewrites later on, you're better off sticking with extensions that are already well-supported.

What To Do When an Extension Isn't Supported

First of all, let's make one thing very clear. Before you use any extension, you need to check to see if it is supported on the user's system. If it's not, you need to do something about it. What that "something" is depends on a number of things, as we'll discuss here, but you really need to have some kind of contingency plan. I've seen OpenGL code that just assumes that the needed extensions will be there. This can lead to blank screens, unexpected rendering effects, and even crashes. Here are some of the possible methods you can use when you find that an extension isn't supported.

Don't Use the Extension
If the extension is non-critical, or if there is simply no alternate way to accomplish the same thing, you may be able to get away with just not using it at all. For example compiled vertex arrays (EXT_compiled_vertex_array) offer potential speed enhancements when using vertex arrays. The speed gains usually aren't big enough to make or break your program, though, so if they aren't supported, you can use a flag or other means to tell your program to not attempt to use them.

Try Similar Extensions
Because of the way that extensions evolve, it's possible that the extension you're trying to use is present under an older name (for example, most ARB extensions used to be EXT extensions, and vendor specific extensions before that). Or, if you're using a vendor-specific extension, there may be extensions from other vendors that do close to the same thing. The biggest drawback to this solution is that it requires a lot of special case code.

Find an Alternate Way
Many extensions were introduced as more efficient ways to do things which could already be done using only core OpenGL features. If you're willing to put in the effort, you can deal with the absence of these extensions by doing things the "old way". For instance, most things that can be done with multitexturing can be done using multipass rendering and alpha blending. Besides the additional code you have to add to handle this, your game will run slower because it has to make multiple passes through the geometry. That's better than not being able to run the game at all, and arguably better than simply dumping multitexturing and sacrificing visual quality.

Exit Gracefully
In some cases, you may decide that an extension is essential to your program, possibly because there is no other way to do the things you want to do, or because providing a backup plan would require more time and effort than you're willing to invest. When this happens, you should cause your program to exit normally, with a message telling the user what they need to be able to play your game. Note that if you choose to go this route, you should make sure that the hardware requirements listed on the product clearly state what is needed, or your customers will hate you.

The Demo

I've created a simple demo to show you some extensions in action. As you can see from Figure 1, the demo itself is fairly simple, nothing more than a light moving above a textured surface, casting a light on it using a lightmap. The demo isn't interactive at all. I kept it simple because I wanted to be able to focus on the extension mechanism.


Figure 1: Basic lightmapping (click to enlarge)

The demo uses seven different extensions. Some of them aren't strictly necessary, but I wanted to include enough to get the point across. Table 2 lists all of the extensions in use, and how they are used.

Table 2 - Extensions used in the demo
Extension    Usage
ARB_multitexture The floor in this demo is a single quad with two textures applied to it: one for the bricks, and the other for the lightmap, which is updated with the light's position. The textures are combined using modulation.
EXT_point_parameteters When used, this extension causes point primitives to change size depending on their distance from the eye. You can set attenuation factors to determine how much the size changes, as well as define maximum and minimum sizes, and even specify that the points become partially transparent if they go below a certain threshold. The yellow light in the demo takes advantage of this extension. The effect is subtle, but you should be able to notice it changing size.
EXT_swap_control Most OpenGL drivers allow the user to specify whether or not screen redraws should wait for the monitor's vertical refresh, or vertical sync. If this is enabled, your game's framerate will be limited to whatever the monitor refresh rate is set to. This extension allows you to programmatically disable vsync to get to avoid this limitation.
EXT_bgra Since the demo uses Targas for textures, using this extension allows it to use their data directly without having to swap the red and blue components before creating the textures.
ARB_texture_compression    Since the demo only uses two textures, it won't gain much by using texture compression, but since it's easy, so I used it anyway. I allow the drivers to compress the data for me, rather than doing so myself beforehand.
EXT_texture_edge_clamp Again, this extension wasn't strictly necessary, but the demo shows how easy it is to use.
SGIS_generate_mipmap GLU provides a function, gluBuild2DMipMaps, that allows you to specify just the base level of a mipmap chain and automatically generates the other levels for you. This extension performs essentially the same function, with a couple of exceptions. One, it is a little more efficient. Two, it will cause all of the mipmap levels to be regenerated automatically whenever you change the base level. This can be useful when using dynamic textures.

The full source code to the demo is included on the CD, but there are a couple of functions that I want to look at.

The first is InitializeExtensions(). This function is called at startup, right after the rendering context is created. It verifies that the extensions used are supported, and gets the function entry points that are needed.

bool InitializeExtensions()
{
if (CheckExtension("GL_ARB_multitexture"))
{
glMultiTexCoord2f = (PFNGLMULTITEXCOORD2FARBPROC)
wglGetProcAddress("glMultiTexCoord2fARB");
glActiveTexture = (PFNGLCLIENTACTIVETEXTUREARBPROC)
wglGetProcAddress("glActiveTextureARB");
glClientActiveTexture = (PFNGLACTIVETEXTUREARBPROC)
wglGetProcAddress("glClientActiveTextureARB");
}
else
{
MessageBox(g_hwnd, "This program requires multitexturing, which "
"is not supported by your hardware", "ERROR", MB_OK);
return false;
}

if (CheckExtension("GL_EXT_point_parameters"))
{
glPointParameterfvEXT = (PFNGLPOINTPARAMETERFVEXTPROC)
wglGetProcAddress("glPointParameterfvEXT");
}

if (CheckExtension("WGL_EXT_swap_control"))
{
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)
wglGetProcAddress("wglSwapIntervalEXT");
}

if (!CheckExtension("GL_EXT_bgra"))
{
MessageBox(g_hwnd, "This program requires the BGRA pixel storage"
"format which is not supported by your hardware", "ERROR", MB_OK);
return false;
}

g_useTextureCompression =
CheckExtension("GL_ARB_texture_compression");
g_useEdgeClamp = CheckExtension("GL_EXT_texture_edge_clamp");
g_useSGISMipmapGeneration =
CheckExtension("GL_SGIS_generate_mipmap");

return true;
}

As you can see, there are two extensions that the demo requires: multitexturing and BGRA pixel formats. Although I could have provided alternate ways to do both of these things, doing so would have unnecessarily complicated the program. The point parameter and swap control extensions aren't required, so I don't exit if they aren't present. Instead, where they are used, I check to see if the function pointer is invalid (i.e., set to NULL). If so, I simply don't use the extension. I use a similar approach with the texture compression, texture edge clamp, and generate mipmap extensions. Since all three of these extensions only introduce new enumrants, I set global flags to indicate whether or not they are supported. When they are used, I check the flag, and if they aren't supported, I use an alternate method. For texture compression, I just use the normal pixel format, for texture edge clamp, I use normal clamping instead, and if the generate mipmaps extension isn't supported, I use gluBuild2DMipmaps() instead.

The other function I want to look at is the CheckExtension() function, which is used repeatedly by InitializeExtensions().

bool CheckExtension(char* extensionName)
{
// get the list of supported extensions
char* extensionList = (char*) glGetString(GL_EXTENSIONS);

if (!extensionName || !extensionList)
return false;

while (*extensionList)
{
// find the length of the first extension substring
unsigned int firstExtensionLength = strcspn(extensionList, " ");

if (strlen(extensionName) == firstExtensionLength &&
strncmp(extensionName, extensionList, firstExtensionLength) == 0)
{
return true;
}

// move to the next substring
extensionList += firstExtensionLength + 1;
}

return false;
}

This function gets the extensions string, and then parses each full extension name string from it, comparing each to the requested extension. Notice that I'm finding each string by looking for the next space to be sure that I don't accidentally match a substring.

Also, this function doesn't check for WGL extensions at all, although it could easily be modified to do so. The code in the demo is not intended to be optimal, nor is it intended to be the "best" way to use extensions. Some people like to make extension function pointers global, as I have done, so that they can be used just like core OpenGL functions anywhere in your program. Others like to put class wrappers around them. Use whatever means you prefer. The demo was intentionally kept as straightforward as possible so that you could easily understand it and take out the parts that interest you.

What You've Learned

You've now seen you how you can use OpenGL's extensions to use the latest features offered by modern video cards. You've learned what some of these features are, and how your game can benefit from them. You've also seen ways in which you can get the most out of extensions without unnecessarily limiting your target audience.

Where To Go Now

I've given you an overview of the features available in OpenGL 1.2, 1.3, and through extensions. I've only been able to cover a few of them in detail. Fortunately, the web is full of resources to help get you started with using these features. The developer sections of the NVIDIA and ATI websites in particular are full of whitepapers, presentations, and demos. In addition, I've found the following websites to be quite useful.

http://www.nutty.org
http://romka.demonews.com
http://www.delphi3d.net

And of course, the front page of OpenGL.org is a great resource for finding links to pages containing OpenGL tutorials and demos.

Conclusion

Now that you have a basic understanding of extensions, I encourage you to spend some time researching them and experimenting on your own. You may find that some of them enable you to significantly improve the efficiency and visual quality of your games.

Acknowledgements

I'd like to thank Alexander Heiner and Mark Shaxted for reviewing this article and correcting some minor inaccuracies, as well as suggesting ways to make it more complete. I'd also like to thank my wife Melissa for making me look like a better writer than I really am.

References

[1] Mark Kilgard, "All About Extensions", http://www.opengl.org/developers/code/features/OGLextensions/OGLextensions.html
[2] NVIDIA Corporation, NVIDIA Developer Relations, http://developer.nvidia.com/
[3] ATI Technologies, ATI Developer Relations, http://www.ati.com/na/pages/resource_centre/dev_rel/devrel.html
[4] OpenGL Architectural Review Board, OpenGL 1.3 Specification, http://www.opengl.org/developers/documentation/specs.html



Dave Astle is the Executive Producer and one of the cofounders of GameDev.net. He is the coauthor of OpenGL Game Programming, has contributed to several other books, and has spoken at game industry conferences such as the Game Developers Conference. He has several years of game industry experience, and is currently a senior engineer on the Graphics and Gaming team at QUALCOMM, Inc. in San Diego, CA.

Discuss this article in the forums

 

posted @ 2010-02-19 12:38  斌伯  阅读(495)  评论(0编辑  收藏  举报