Section 1.1. What Is OpenGL?


1.1. What Is OpenGL?

OpenGL is the graphics API defined by The OpenGL Graphics System: A Specification. OpenGL programs use implementations of this specification for display of 2D and 3D geometric data and images.

According to the OpenGL specification, "To the programmer, OpenGL is a set of commands that allow the specification of geometric objects in two or three dimensions, together with commands that control how these objects are rendered into the framebuffer."[1] OpenGL is typically implemented as a library of entry points (the GL in OpenGL stands for Graphics Library [Kilgard 1997]) and graphics hardware to support that library, as Figure 1-1 shows.

[1] Segal, Mark and Kurt Akeley, The OpenGL Graphics System: A Specification, version 2.0, September 2004, p. 2.

Figure 1-1. Two typical OpenGL implementations. In (a), applications link with a library of OpenGL entry points that pass commands to OpenGL-based graphics hardware. (b) illustrates a clientserver implementation. Applications link with a library of OpenGL entry points that pass commands over network protocol. A server (local or remote) receives the commands and renders using server hardware.


In computer systems designed for 3D graphics, the hardware directly supports almost all OpenGL features. As a result, the OpenGL library is merely a thin layer, allowing the application to access hardware functionality efficiently. High-level features that are difficult to implement in hardwaresuch as support for high-level primitive types, scene graphs, and utility functionsare not part of the OpenGL specification (Carson 1997). Several libraries to support such high-level functionality are available, however. See the section "GLU" later in this chapter for an overview of one such library.

OpenGL doesn't include support for windowing, input (mouse, keyboard, and so on), or user interface functionality, as computer systems typically provide platform-specific support for these features. The GLUT library (see the section "GLUT" later in this chapter) provides platform-independent support for this functionality and is sufficient for most small-application and demo-program needs. This book covers platform-specific libraries in Chapter 8, "Platform-Specific Interfaces."

1.1.1. Fundamentals and Architecture

OpenGL is a state machine. Applications call OpenGL functions to set OpenGL state, which in turn determines the final appearance of a primitive in the framebuffer. An application might set the color to red, draw a point primitive, set the color to blue, and draw a second and third point primitive, as follows:

 glColor3f( 1.f, 0.f, 0.f ); // red as an RGB triple glBegin( GL_POINTS );   glVertex3f( -.5f, 0.f, 0.f ); // XYZ coordinates of first point glEnd(); glColor3f( 0.f, 0.f, 1.f ); // blue as an RGB triple glBegin( GL_POINTS );   glVertex3f( 0.f, 0.f, 0.f ); // XYZ coordinates of second point glEnd(); glBegin( GL_POINTS );   glVertex3f( .5f, 0.f, 0.f ); // XYZ coordinates of third point glEnd(); 


In this case, OpenGL displays the first point red and the second point blue. Because the code doesn't change the color state before the third point, OpenGL also draws it blue. (glColor3f() specifies an RGB color value. glVertex3f() specifies an xyz vertex location. This code uses glBegin() and glEnd() to denote individual primitives, but more efficient methods are covered in Chapter 2.)

Primitives are groups of one or more vertices. In the example above, a single vertex is used for each point. Line and fill primitives require two or more vertices. Vertices have their own color, texture coordinates, and normal state (as well as other per-vertex states). You could rewrite the above code for drawing a red and blue point as follows:

 glBegin( GL_POINTS );   glColor3f( 1.f, 0.f, 0.f ); // red as an RGB triple   glVertex3f( -.5f, 0.f, 0.f ); // XYZ coordinates of first point   glColor3f( 0.f, 0.f, 1.f ); // blue as an RGB triple   glVertex3f( .5f, 0.f, 0.f ); // XYZ coordinates of second point glEnd(); 


OpenGL always executes commands in the order in which the application sends them. In a clientserver implementation (refer to Figure 1-1), these commands can be buffered on the client side and might not execute immediately, but applications can force OpenGL to execute buffered commands by calling glFlush() or swapping buffers.

Like modern CPUs, OpenGL rendering uses a pipeline architecture. OpenGL processes pixel and vertex data using four main stagesper-vertex operations, pixel operations, rasterization, and per-fragment operationsand stores the results in the framebuffer or texture memory (see Figure 1-2).

Figure 1-2. The OpenGL pipeline architecture.


Applications pass two types of data to OpenGL for rendering: vertex data and pixel data. They use vertex data to render 2D and 3D geometric primitives, such as points, lines, and filled primitives. (See Chapter 2, "Drawing Primitives," for more information.) Applications specify pixel data as arrays of pixels and can direct OpenGL to display the pixels directly to the framebuffer or copy them to texture memory for later use as texture maps. Additionally, applications can read pixel data from the framebuffer or copy portions of the framebuffer into texture memory. Chapter 5, "Pixel Rectangles," and Chapter 6, "Texture Mapping," cover this subject in greater depth.

Note

OpenGL has always featured a fixed-function pipelinea fixed set of functionality controlled by the application via OpenGL state. Starting with version 2.0, however, OpenGL allows applications to override certain per-vertex and per-fragment operations with vertex and fragment shaderssmall programs written using a shading language. OpenGL® Shading Language is the definitive resource for writing shaders in OpenGL, and appendix A, "Other Features," briefly discusses this subject.

OpenGL® Distilled covers only the fixed-function pipeline; it doesn't cover shaders, which are beyond the scope of this book.


1.1.1.1. Per-Vertex Operations

OpenGL performs the following operations on each vertex it receives from your application:

  • Transformation OpenGL transforms each vertex from object-coordinate space to window-coordinate space. This is a gross oversimplification of the transformation process, which is covered in detail in Chapter 3, "Transformation and Viewing."

  • Lighting If the application has enabled lighting, OpenGL calculates a lighting value at each vertex. Lighting is discussed in Chapter 4, "Lighting."

  • Clipping When OpenGL determines that an entire primitive is not visible because it is outside the view volume, OpenGL discards all vertices. If a primitive is partially visible, OpenGL clips the primitive so that only the visible portion is rasterized.

After per-vertex operations, OpenGL rasterizes the primitive and performs per-fragment operations on the result.

1.1.1.2. Pixel Operations

OpenGL performs pixel storage operations on all blocks of pixel data that applications send to and receive from OpenGL. These operations control byte swapping, padding, and offsets into blocks of pixel data to support sending and receiving pixels in a wide variety of formats.

Other pixel operations, such as pixel transfer (mapping, scaling, and biasing) and the optional imaging subset, are outside the scope of this book. See Chapter 8, "Drawing Pixels, Bitmaps, Fonts, and Images," in OpenGL® Programming Guide for more information.

1.1.1.3. Rasterization

Rasterization converts geometric data into fragments. Fragments are position, color, depth, texture coordinate, and other data that OpenGL processes before eventually writing into the framebuffer. Contrast this with pixels, which are the physical locations in framebuffer memory where fragments are stored. Typically, OpenGL associates a fragment with a single pixel location. OpenGL implementations that support multisampling, however, store fragments in subpixel locations.

Rasterization rules are covered in Chapter 3, "Rasterization," of The OpenGL Graphics System. Most programmers won't need to know these rules unless they're developing applications that require exact pixelization.

OpenGL specifies rasterization at the subpixel level; it treats point primitives as mathematical points, line primitives as mathematical lines, and filled primitives as areas bounded by mathematical lines. Multisampling aside, the programmer should visualize window-coordinate space as a Cartesian grid in which each grid cell corresponds to a pixel on the screen. With this in mind, OpenGL performs rasterization as follows:

  • When rasterizing point primitives, OpenGL produces a fragment if the point lies within the pixel boundary.

  • OpenGL rasterizes a line by producing fragments that lie between its endpoints. As a general rule, OpenGL doesn't produce a fragment corresponding to the second, or triggering, vertex in a line segment. This ensures that connected line segments with the same slope won't paint the same pixel twice.

  • OpenGL produces fragments for filled primitives if the pixel center lies within the mathematical boundary of the primitive. Special rules for pixel centers that lie on the mathematical boundary ensure that two nonoverlapping filled primitives sharing an edge won't paint the same pixel twice.

  • When rasterizing blocks of pixel data at the default zoom level, OpenGL produces a fragment for each pixel in the pixel block.

This is a simplification of the rasterization process. OpenGL® Distilled doesn't cover features such as smooth points, lines, and polygons; wide points and lines; and pixel zoom, all of which affect the rasterization stage. OpenGL® Programming Guide covers these features in detail.

1.1.1.4. Per-Fragment Operations

OpenGL performs significant processing on each fragment to determine its final framebuffer values and whether or not to write it into the framebuffer.

For every fragment produced by rasterization, OpenGL performs the following operations:

  • Pixel ownership test This test allows OpenGL to display correctly in the presence of multiple overlapping windows. Fragments corresponding to framebuffer locations that are obscured by an overlapping window can be discarded or saved in a backing store.

  • Scissor test If the framebuffer location lies outside an application-defined window-coordinate rectangle, OpenGL discards the fragment.

  • Multisample fragment operations OpenGL modifies fragment alpha and coverage values according to multisampling rules.

  • Alpha test If the fragment alpha value does not pass application-specified criteria, OpenGL discards it. This is useful for discarding completely or partially transparent fragments.

  • Stencil test OpenGL compares the stored stencil value at the framebuffer location and uses the current application state to determine what to do with the fragment and how to modify the stored stencil value. The stencil test has many uses, including nonrectangular clipping, shadows, and constructive solid geometry (CSG).

  • Depth test The depth test discards a fragment if a comparison between the fragment depth value and the value stored in the depth buffer fails. The OpenGL depth test is a form of z-buffering.

  • Occlusion query When an occlusion query is active, OpenGL increments a count for each fragment that passes the depth test. Occlusion queries allow fast visibility testing in complex scenes.

  • Blending Blending combines the fragment RGB color and alpha values with the RGB and alpha values stored at the framebuffer location. Applications typically use blending to simulate translucent objects.

  • Dithering When the framebuffer has less color precision than the fragment RGB and alpha values, OpenGL dithers the fragment color and alpha, using a repeatable algorithm. This feature is rarely used due to the widespread availability of 24-bit framebuffers.

  • Logical operation OpenGL writes the final color value into the framebuffer according to an application-specified logical operation.

As a concise guide to OpenGL, OpenGL® Distilled doesn't cover all fragment operations. For information on scissor, multisample, stencil, occlusion query, dithering, and logical operations, see OpenGL® Programming Guide.

1.1.2. Syntax

OpenGL is supported by several programming languages, each with its own binding. This book and the C++ example code use the C-binding.

The C-binding syntax rules are as follows.

1.1.2.1. Types

The C-binding prefixes all OpenGL type names with GL. The Boolean type is GLboolean, for example, and the double-precision floating-point type is GLdouble. The GLbyte, GLshort, and GLint types have unsigned analogues: GLubyte, GLushort, and GLuint.

Table 1-1 summarizes some common OpenGL data types.

Table 1-1. OpenGL Data Types

OpenGL Type

Minimum Number of Bits

Command Suffix

Description

GLboolean

1

NA

Boolean

GLbyte

8

b

Signed integer

GLubyte

8

ub

Unsigned integer

GLshort

16

s

Signed integer

GLushort

16

us

Unsigned integer

GLsizei

32

NA

Non-negative integer size

GLsizeiptr

Number of bits in a pointer

NA

Pointer to a non-negative integer size

GLint

32

i

Signed integer

GLuint

32

ui

Unsigned integer

GLfloat

32

f

Floating point

GLclampf

32

NA

Floating point clamped to the range [0, 1].

GLenum

32

NA

Enumerant

GLbitfield

32

NA

Packed bits

GLdouble

64

d

Floating point

GLvoid*

Number of bits in a pointer

NA

Pointer to any data type; equivalent to "void*" in C/C++.


1.1.2.2. Commands

The C-binding implements OpenGL commands as C-callable functions prefixed with gl. To execute the OpenGL Enable command, for example, your application calls the glEnable() function. OpenGL® Distilled uses the phrases "OpenGL commands" and "OpenGL function calls" synonymously.

In general, OpenGL doesn't overload commands.[2] To support commands with identical functionality but different numbers and types of argument, OpenGL suffixes command names with up to four characters. The first character indicates the number of arguments; the second character or pair of characters indicates the parameter type; and the final character, if present, is v, which indicates that the function takes an address as an argument.

[2] OpenGL version 1.5 overloads commands that access buffer objects. Chapter 2, "Drawing Primitives," and Chapter 7, "Extensions and Versions," demonstrate this.

For example:

 // Specify an RGB color value with three floats: GLfloat red=1.f, green=1.f, blue=1.f; glColor3f( red, green, blue ); // Specify an RGBA color value with four unsigned bytes: GLubyte r=255, g=255, b=255, a=255; glColor4ub( r, g, b, a ); // Specify an RGB value with the address of three shorts: GLshort white[3] = { 32767, 32767, 32767 }; glColor3sv( white ); 


1.1.2.3. Enumerants

The C-binding defines OpenGL enumerants using the C preprocessor and prefixes enumerant names with GL_. To query OpenGL for its version string using the VERSION enumerant, for example, your application calls glGetString( GL_VERSION );.

1.1.3. State and Queries

As a state machine, OpenGL won't function without a place to store current state values. OpenGL stores its state in a GL context. Platform-specific interfaces allow you to create, destroy, and copy rendering contexts that embody the GL context. Chapter 8, "Platform-Specific Interfaces," covers how to manage rendering contexts. This book, however, uses the GLUT library, which implicitly manages rendering contexts. See the section "GLUT" later in this chapter for more information. For now, all you need to know is that OpenGL stores state in a rendering context. As your application issues commands that change OpenGL state, those changes are kept in the current rendering context.

OpenGL provides several commands for setting state. The most common commands programmers encounter are glEnable() and glDisable().


void glEnable( GLenum target );
void glDisable( GLenum target );


Use these commands to set the enable state of OpenGL features. target specifies which feature to enable or disable. This book describes relevant target enumerants as it covers various features throughout the book. For a complete list of valid target enumerants, see "glEnable" in OpenGL® Reference Manual.

As an example, call glEnable( GL_DEPTH_TEST ) to enable the depth test (or z-buffering) feature. After your application issues this command, OpenGL renders subsequent primitives using the depth test. Later, if your code calls glDisable( GL_DEPTH_TEST ) to disable the feature, followed by other primitives, OpenGL won't use the depth test to render them.

Most OpenGL features are disabled by default. In fact, only two features are enabled by default: dithering (GL_DITHER) and multisampling (GL_MULTISAMPLE).

1.1.3.1. Using glIsEnabled

Query whether a feature is currently enabled or disabled with the glIsEnabled() command.


GLboolean glIsEnabled( GLenum value );


Returns whether an OpenGL feature is enabled or disabled. value is any OpenGL enumerant that is valid as the target parameter in glEnable()/glDisable(). glIsEnabled() returns GL_TRUE if value is enabled and GL_FALSE if value is disabled.

Your application enables and disables lighting with glEnable ( GL_LIGHTING ) and glDisable( GL_LIGHTING ), respectively. To query the current state of lighting, call glIsEnabled( GL_LIGHTING ).

1.1.3.2. Using glGet

Applications might need to query OpenGL state for any number of reasons. Usually, applications query OpenGL at init time to obtain implementation-specific values, such as GL_MAX_ATTRIB_STACK_DEPTH or GL_MAX_LIGHTS. OpenGL lets the application query any value that the application can set. The application could obtain the current color (set with glColor3f()) by querying GL_CURRENT_COLOR.

OpenGL provides a general mechanism for querying various state values.

void glGetBooleanv( GLenum pname, GLboolean* param );
void glGetDoublev( GLenum pname, GLdouble* param );
void glGetFloatv( GLenum pname, GLfloat* param );
void glGetIntegerv( GLenum pname, GLint* param );


This set of routines allows you to query OpenGL state values by type.

pname designates the state item to query, and param points to a GLboolean, GLdouble, GLfloat, or GLint location in memory to hold the state value.

Use the function that fits the type of the state item being queried. GL_MAX_LIGHTS is an integer value, so your code would use glGetIntegerv():

 GLint maxLights; glGetIntegerv( GL_MAX_LIGHTS, &maxLights ); 


Throughout the book, OpenGL® Distilled describes various state items that your application can query. For a complete list, see "glGet" in OpenGL® Reference Manual and Chapter 6, "State and State Requests," in The OpenGL Graphics System.

Note

Applications should use queries sparingly in performance-sensitive code sections.[3]

[3] Occlusion queries can actually boost application performance, however. See appendix A, "Other Features," for more information.


1.1.3.3. The Attribute Stack

OpenGL features stack data structures for saving and restoring state changes. The top of the attribute stacks contain the current OpenGL state. After your application has pushed the stack and made state changes, popping the stack restores OpenGL to its state before the push command.

There is one stack for OpenGL server state, and another for OpenGL client state. Chapter 2, "Drawing Primitives," describes the differences between client and server state.


void glPushAttrib( GLbitfield mask );
void glPopAttrib( void );
void glPushClientAttrib( GLbitfield mask );
void glPopClientAttrib( void );


Pushes and pops OpenGL state stacks. mask is a bitwise OR of OpenGL-defined bit values representing groups of state to be pushed. The server and client stacks save and restore different state values. glPushAttrib()/glPopAttrib() affect the server stack, whereas glPushClientAttrib()/glPopClientAttrib() affect the client stack.

glPushAttrib()/glPopAttrib(), 1.0 and later;
glPushClientAttrib()/glPopClientAttrib(), 1.1 and later.


The mask parameter allows your application to specify which sets of state values are saved and restored by the push/pop commands. Calling glPushAttrib( GL_LIGHTING_BIT ) causes the corresponding glPopAttrib() to restore only state values that affect OpenGL lighting, such as changes to material and light properties.

To push all stackable server state, call glPushAttrib() with a mask of GL_ALL_ATTRIB_BITS. Likewise, push all stackable client state by calling glPushClientAttrib( GL_CLIENT_ALL_ATTRIB_BITS ).

Although the OpenGL specification states that glPushAttrib()/glPushClientAttrib() act as though they were copying the current OpenGL state onto the new top of stack, most modern OpenGL products implement this with minimal data copying. As a result, pushing all state is not as expensive as you might expect. Although pushing all state is certainly more expensive than pushing some state, developers often find that the reusability benefits of GL_ALL_ATTRIB_BITS and GL_CLIENT_ALL_ATTRIB_BITS outweigh the performance cost. For information on all the valid mask values, see "glPushAttrib" and "glPushClientAttrib" in OpenGL® Reference Manual and Chapter 6, "State and State Requests," in The OpenGL Graphics System.

The attribute stacks have an implementation-specific depth of at least 16. Your application can query the maximum attribute stack depths by calling glGetIntegerv( GL_MAX_ATTRIB_STACK_DEPTH, ... ) and glGetIntegerv( GL_MAX_CLIENT_ATTRIB_STACK_DEPTH, ... ).

1.1.3.4. Using glGetString

OpenGL provides information about the implementation in string form. Use glGetString() to query these values.


const GLubyte* glGetString( GLenum name );


Returns OpenGL string-based state values. name is the string state item to query.

glGetString() returns different values depending on the value of name:

  • GL_VENDOR Returns the manufacturer of the OpenGL implementation

  • GL_VERSION Returns the OpenGL version

  • GL_EXTENSIONS Returns extensions available in the OpenGL implementation

  • GL_RENDERER Returns vendor-specific renderer information

(GL_SHADING_LANGUAGE_VERSION is another possible value for name; for more information, see OpenGL® Shading Language.)

Applications typically query GL_VERSION and GL_EXTENSIONS at init time to determine the supported feature set. GL_EXTENSIONS will be covered in more detail in Chapter 7, "Extensions and Versions."

Note

As described in the section "State and Queries" earlier in this chapter, OpenGL stores state in a rendering context. It's common for beginners to call glGetString() accidentally to query OpenGL's version and supported extensions at init time without a current rendering context. In this case, behavior is undefined, and glGetString() commonly returns NULL. To prevent this, don't call glGetString() until after calling glutCreateWindow(), which creates a rendering context and makes it current in GLUT.


The version string returned by glGetString( GL_VERSION ) is either major_number.minor_number or major_number.minor_number.release_number and is optionally followed by a space and additional vendor-specific information. Applications can parse the version string to obtain the major and minor version numbers with the following code:

 std::string ver((const char*) glGetString(GL_VERSION)); assert( !ver.empty() ); std::istringstream verStream( ver ); int major, minor; char dummySep; verStream >> major >> dummySep >> minor; 


After parsing the version string, applications can determine what OpenGL feature set to use based on the major and minor version numbers. Because major_version 1 and minor_version 4 indicate OpenGL version 1.4, for example, you wouldn't be able to use the OpenGL version 1.5 core interface for features like buffer objects and occlusion queries in your application.

As of this printing, the possible version numbers for OpenGL are 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, and 2.0.

1.1.3.5. Querying Identifiers

OpenGL generates identifiers for buffers, display lists, and textures. Your application can query whether an identifier is a valid buffer, display list, or texture by calling glIsBuffer(), glIsList(), and glIsTexture(), respectively. These functions are covered in later chapters.

1.1.3.6. Other Query Functions

Other OpenGL query functions allow the application to obtain lighting and texture mapping-specific values. Chapter 4, "Lighting," and Chapter 6, "Texture Mapping," discuss setting lighting and texture mapping state parameters, but OpenGL® Distilled doesn't cover querying these values. See "glGetLight," "glGetMaterial," "glGetTexEnv," "glGetTexGen," "glGetTexImage," and "glGetTexParameter" in OpenGL® Reference Manual.

1.1.3.7. Errors

There are several ways for your application to generate errors in OpenGL. If your application generates an error that OpenGL detects, you can query the error with glGetError().


GLenum glGetError( void );


Returns the current error state. Error values returned by glGetError() are summarized in Table 1-2. If OpenGL hasn't recorded an error, glGetError() will return GL_NO_ERROR.

When OpenGL detects an error, it records an error code and continues processing commands. Your application can retrieve the recorded error code by calling glGetError(). It returns the error code as a GLenum.

Table 1-2 summarizes OpenGL errors.

Table 1-2. OpenGL Error Codes Returned by glGetError()[a]

GLenum Error Code

Description

GL_INVALID_ENUM

An invalid enumerant was passed to an OpenGL command.

GL_INVALID_OPERATION

An OpenGL command was issued that was invalid or inappropriate for the current state.

GL_INVALID_VALUE

A value was passed to OpenGL that was outside the allowed range.

GL_OUT_OF_MEMORY

OpenGL was unable to allocate enough memory to process a command.

GL_STACK_OVERFLOW

A command caused an OpenGL stack to overflow.

GL_STACK_UNDERFLOW

A command caused an OpenGL stack to underflow.


[a] In addition to the errors listed, OpenGL might return the error GL_TABLE_TOO_LARGE. See The OpenGL Graphics System for more information.

For example, if your application calls

 glEnable( GL_MAX_LIGHTS ); 


OpenGL records the error GL_INVALID_ENUM, because GL_MAX_LIGHTS is a valid enumerant for glGetIntegerv(), not for glEnable().

OpenGL saves error codes until they are retrieved with glGetError(). If your application retrieves an error from glGetError(), any of several preceding OpenGL commands could have generated it.

When an OpenGL command generates an error, OpenGL behavior is usually well defined: The offending call acts as a no-op (the function has no effect on OpenGL state or the framebuffer), and if it returns a value, it returns zero. If the error GL_OUT_OF_MEMORY occurs, however, OpenGL is left in an undefined state.[4]

[4] If glEndList() generates GL_OUT_OF_MEMORY, OpenGL state is defined. See "glEndList" in the OpenGL Reference Manual for details.

Typical Usage

Treat OpenGL errors as bugs in your application that you need to fix. Production code shouldn't generate OpenGL errors.

Applications typically call glGetError() after any OpenGL initialization code and at the end of each rendered frame in an animation. Programmers typically only call glGetError() in nonproduction code to avoid negatively affecting application performance. This can be done with an assert() call:

 assert( glGetError() == GL_NO_ERROR ); 


Programmers typically use CPP macros to check OpenGL errors and take some action, such as display an error dialog box or throw an exception, with the macro defined as a no-op in production code. The example code that comes with this book demonstrates this strategy with the OGLDIF_CHECK_ERROR CPP macro. appendix D, "Troubleshooting and Debugging," shows the declaration of this macro and also provides debugging tips for code that generates OpenGL errors.




OpenGL Distilled
OpenGL Distilled
ISBN: 0321336798
EAN: 2147483647
Year: 2007
Pages: 123
Authors: Paul Martz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net