OpenGL Distilled

7.3. Extension- and Version-Safe Code

To create a single application that runs in multiple OpenGL implementations, your application must query OpenGL for version and extension support at runtime using glGetString(). If you intend to write code for distribution to others, however, you also need to make sure the code will compile in an uncontrolled development environment where extension- and version-specific definitions and declarations might not exist.

In the C binding for OpenGL, the gl.h and glext.h header files define C preprocessor symbols to indicate extension and version support. This allows your code to compile extension- and version-specific code conditionally only when the development environment supports it.

C preprocessor definitions for extensions are the same as the extension name. If the development environment contains enumerant definitions and entry-point declarations for the GL_ARB_multisample extension, for example, it defines GL_ARB_multisample as a C preprocessor symbol.

To indicate supported versions, the development environment defines possibly multiple symbols of the form GL_VERSION_<major>_<minor>, where <major> and <minor> indicate the major and minor OpenGL version number. If the development environment supports version 2.0, it defines GL_VERSION_2_0 as a C preprocessor symbol. Because each version of OpenGL is backward compatible with previous versions, development environments define a symbol for each supported versionGL_VERSION_1_0, GL_VERSION_1_1, and so onup to and including a symbol for the latest supported version.

Listing 7-1 shows the PixelBuffer class, part of the example code available from the OpenGL® Distilled Web site. PixelBuffer uses an extension feature and requires a specific OpenGL version but compiles in any OpenGL development environment and runs in any OpenGL implementation.

PixelBuffer uses the GL_ARB_pixel_buffer_object[3] extension to speed pixel rectangle rendering using the glDrawPixels() command. GL_ARB_pixel_buffer_object is similar to the OpenGL version 1.5 buffer object feature and requires buffer object entry points such as glGenBuffers() and glBufferData(). Buffer objects allow vertex array commands to source vertex data from high-performance server memory rather than client memory. GL_ARB_pixel_buffer_object extends this functionality to commands that source pixel data, such as glDrawPixels(). Because pixel data typically is large, GL_ARB_pixel_buffer_object significantly increases performance of commands such as glDrawPixels().

[3] GL_ARB_pixel_buffer_object was promoted from EXT to ARB status in the December 2004 OpenGL ARB meeting. It is a candidate for promotion to the OpenGL version 2.1 specification.

PixelBuffer is derived from Pixels, which uses glDrawPixels() without the GL_ARB_pixel_buffer_object extension. So if the development environment doesn't support the GL_ARB_pixel_buffer_object extension or version 1.5, PixelBuffer doesn't compile the extension- and version-specific code; instead, it simply falls back to its base class.

Listing 7-1. Example of extension- and version-safe code

#if defined( GL_ARB_pixel_buffer_object ) && defined( GL_VERSION_1_5 ) # define PIXELBUFFER_BUILD_PBO 1 #endif class PixelBuffer : public Pixels { public: PixelBuffer(); private: GLuint _pbo; #ifdef PIXELBUFFER_BUILD_PBO public: virtual ~PixelBuffer(); virtual void apply(); protected: virtual bool init(); #endif // PIXELBUFFER_BUILD_PBO }; PixelBuffer::PixelBuffer() : Pixels(), _pbo( 0 ) { } #ifdef PIXELBUFFER_BUILD_PBO PixelBuffer::~PixelBuffer() { if ( _valid ) { assert( _pbo != 0 ); glDeleteBuffers( 1, &_pbo ); } } void PixelBuffer::apply() { if (!_valid) { if (!init()) return; } assert( _pbo != 0 ); glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, _pbo ); glDrawPixels( _width, _height, _format, _type, bufferObjectPtr( 0 ) ); OGLDIF_CHECK_ERROR; } bool PixelBuffer::init() { Pixels::init(); if (!_valid) return false; glGenBuffers( 1, &_pbo ); assert( _pbo != 0 ); glBindBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, _pbo ); glBufferData( GL_PIXEL_UNPACK_BUFFER_ARB, size(), _pixels, GL_STATIC_DRAW ); return _valid; } #endif // PIXELBUFFER_BUILD_PBO

Because the code that uses pixel buffer objects requires support for both the GL_ARB_pixel_buffer_object extension and version 1.5, the code defines a C preprocessor symbol, PIXELBUFFER_BUILD_PBO, only if the development environment supports both the required extension and version.

The destructor and the apply() and init() methods are declared virtual in the base class. If PIXELBUFFER_BUILD_PBO is defined, PixelBuffer overrides them. Otherwise, instantiations of this class use the base-class member functions.

The init() member function is called before any rendering. It allocates a buffer object using glGenBuffers() and binds the buffer to an extension-specific target, GL_PIXEL_UNPACK_BUFFER_ARB. According to the GL_ARB_pixel_buffer_object specification, when a buffer object is bound to GL_PIXEL_UNPACK_BUFFER_ARB, commands that receive pixel data from the application, such as glDrawPixels(), source data from the bound buffer object rather than client memory. After binding the buffer, init() stores the pixel rectangle in the bound buffer object using the glBufferData() command.

The apply() method renders the pixel rectangle. It binds the buffer object and calls glDrawPixels() with a pointer to NULL. The class destructor deletes the buffer object.

The code also needs to handle runtime environments that lack the requisite runtime support. Listing 7-1 by itself would fail in a runtime environment that either lacks the GL_ARB_pixel_buffer extension or has an OpenGL version less than 1.5. For this reason, the base Pixels class contains a static factory method called Pixels::create(), shown in Listing 7-2, that ensures that PixelBuffer will be instantiated only if the runtime environment can support it. This simplifies the implementation of PixelBuffer by eliminating redundant runtime checks for the necessary OpenGL version and presence of the GL_ARB_pixel_buffer extension.

Listing 7-2. Factory method for the PixelBuffer class

class Pixels { public: typedef enum { NoPBO, UsePBOIfAvailable, ForcePBO } PixelsProduct; static Pixels* create( PixelsProduct product=UsePBOIfAvailable ); ... }; Pixels* Pixels::create( PixelsProduct product ) { if (product == NoPBO) return new Pixels(); const std::string pboStr( "GL_ARB_pixel_buffer_object" ); const bool pboAvailable = ( (OGLDif::instance()->getVersion() >= Ver15) && (OGLDif::instance()->isExtensionSupported( pboStr, glGetString( GL_EXTENSIONS ) )) ); if (pboAvailable) return new PixelBuffer(); if (product == UsePBOIfAvailable) // PBO not supported, use Pixels instead return new Pixels(); // product == ForcePBO, but it's not supported return NULL; }

Pixels::create() implements a parameterized factory design pattern. Calling code can pass in the level of GL_ARB_pixel_buffer support desired. If the calling code does not want the Pixels object to use GL_ARB_pixel_buffer, it passes NoPBO as a parameter, and create() responds by returning an instantiation of the Pixels base class.

Otherwise, create() checks for runtime extension support using the isExtensionSupported() function. If the OpenGL implementation supports the extension and supports OpenGL version 1.5, it sets the local variable pboAvailable to true. The code then uses the value of pboAvailable, along with the product parameter, to determine whether to instantiate PixelBuffer, Pixels or simply return NULL. The logic in this code ensures that PixelBuffer is instantiated only if the runtime environment supports GL_ARB_pixel_buffer and OpenGL version 1.5 or later.

Категории