Framebuffer Objects - OpenGL ES 3.0: Programming Guide, Second Edition (2014)

OpenGL ES 3.0: Programming Guide, Second Edition (2014)

Chapter 12. Framebuffer Objects

In this chapter, we describe what framebuffer objects are, how applications can create them, and how applications can use them for rendering to an off-screen buffer or rendering to a texture. We start by discussing why we need framebuffer objects. We then introduce framebuffer objects and new object types they add to OpenGL ES, and explain how they differ from the EGL surfaces described in Chapter 3, “An Introduction to EGL.” We go on to discuss how to create framebuffer objects; explore how to specify color, depth, and stencil attachments to a framebuffer object; and then provide examples that demonstrate rendering to a framebuffer object. Last but not least, we discuss performance tips and tricks that can help ensure good performance when using framebuffer objects.

Why Framebuffer Objects?

A rendering context and a drawing surface need to be first created and made current before any OpenGL ES commands can be called by an application. The rendering context and the drawing surface are usually provided by the native windowing system through an API such as EGL. Chapter 3 describes how to create an EGL context and surface and how to attach them to a rendering thread. The rendering context contains the appropriate state required for correct operation. The drawing surface provided by the native windowing system can be a surface that will be displayed on the screen, referred to as the window system–provided framebuffer, or it can be an off-screen surface, referred to as a pbuffer. The calls to create the EGL drawing surfaces let you specify the width and height of the surface in pixels; whether the surface uses color, depth, and stencil buffers; and the bit depths of these buffers.

By default, OpenGL ES uses the window system–provided framebuffer as the drawing surface. If the application is drawing only to an on-screen surface, the window system–provided framebuffer is usually sufficient. However, many applications need to render to a texture, and for this purpose using the window system–provided framebuffer as your drawing surface is usually not an ideal option. Examples of where the render-to-texture approach is useful are shadow mapping, dynamic reflections and environment mapping, multipass techniques for depth-of-field, motion blur effects, and postprocessing effects.

Applications can use either of two techniques to render to a texture:

• Implement render to texture by drawing to the window system–provided framebuffer and then copy the appropriate region of the framebuffer to the texture. This can be implemented using the glCopyTexImage2D and glCopyTexSubImage2D APIs. As their names imply, these APIs perform a copy from the framebuffer to the texture buffer, and this copy operation can often adversely impact performance. In addition, this approach works only if the dimensions of the texture are less than or equal to the dimensions of the framebuffer.

• Implement render to texture by using a pbuffer that is attached to a texture. We know that a window system–provided surface must be attached to a rendering context. This can be inefficient on some implementations that require separate contexts for each pbuffer and window surface. Additionally, switching between window system–provided drawables can sometimes require the implementation to flush all previous rendering prior to the switch. This can introduce expensive “bubbles” (idling the GPU) into the rendering pipeline. On such systems, our recommendation is to avoid using pbuffers to render to textures because of the overhead associated with context- and window system–provided drawable switching.

Neither of these two methods is ideal for rendering to a texture or other off-screen surface. What is needed instead are APIs that allow applications to directly render to a texture or the ability to create an off-screen surface within the OpenGL ES API and use it as a rendering target. Framebuffer objects and renderbuffer objects allow applications to do exactly this, without requiring additional rendering contexts to be created. As a consequence, we no longer have to worry about the overhead of a context and drawable switch that can occur when using window system–provided drawables. Framebuffer objects, therefore, provide a better and more efficient method for rendering to a texture or an off-screen surface.

The framebuffer objects API supports the following operations:

• Creating framebuffer objects using OpenGL ES commands only

• Creating and using multiple framebuffer objects within a single EGL context—that is, without requiring a rendering context per framebuffer

• Creating off-screen color, depth, or stencil renderbuffers and textures, and attaching these to a framebuffer object

• Sharing color, depth, or stencil buffers across multiple framebuffers

• Attaching textures directly to a framebuffer as color or depth, thereby avoiding the need to do a copy operation

• Copying between framebuffers and invalidating framebuffer contents

Framebuffer and Renderbuffer Objects

In this section, we describe what renderbuffer and framebuffer objects are, explain how they differ from window system–provided drawables, and consider when to use a renderbuffer instead of a texture.

A renderbuffer object is a 2D image buffer allocated by the application. The renderbuffer can be used to allocate and store color, depth, or stencil values and can be used as a color, depth, or stencil attachment in a framebuffer object. A renderbuffer is similar to an off-screen window system–provided drawable surface, such as a pbuffer. A renderbuffer, however, cannot be directly used as a GL texture.

A framebuffer object (FBO) is a collection of color, depth, and stencil textures or render targets. Various 2D images can be attached to the color attachment point in the framebuffer object. These include a renderbuffer object that stores color values, a mip level of a 2D texture or a cubemap face, a layer of a 2D array textures, or even a mip level of a 2D slice in a 3D texture. Similarly, various 2D images containing depth values can be attached to the depth attachment point of an FBO. These can include a renderbuffer, a mip level of a 2D texture, or a cubemap face that stores depth values. The only 2D image that can be attached to the stencil attachment point of an FBO is a renderbuffer object that stores stencil values.

Figure 12-1 shows the relationships among framebuffer objects, renderbuffer objects, and textures. Note that there can be only one color, depth, and stencil attachment in a framebuffer object.

Image

Figure 12-1 Framebuffer Objects, Renderbuffer Objects, and Textures

Choosing a Renderbuffer Versus a Texture as a Framebuffer Attachment

For render-to-texture use cases, you would attach a texture object to the framebuffer object. Examples include rendering to a color buffer that will be used as a color texture, and rendering into a depth buffer that will be used as a depth texture for shadows.

There are several reasons to use renderbuffers instead of textures:

• Renderbuffers support multisampling.

• If the image will not be used as a texture, using a renderbuffer may deliver a performance advantage. This advantage occurs because the implementation might be able to store the renderbuffer in a much more efficient format, better suited for rendering than for texturing. The implementation can only do so, however, if it knows in advance that the image will not be used as a texture.

Framebuffer Objects Versus EGL Surfaces

The differences between an FBO and the window system–provided drawable surface are as follows:

• Pixel ownership test determines whether the pixel at location (xw, yw) in the framebuffer is currently owned by OpenGL ES. This test allows the window system to control which pixels in the framebuffer belong to the current OpenGL ES context—for example, when a window that is being rendered into by OpenGL ES is obscured. For an application-created framebuffer object, the pixel ownership test always succeeds, as the framebuffer object owns all the pixels.

• The window system might support only double-buffered surfaces. Framebuffer objects, in contrast, support only single-buffered attachments.

• Sharing of stencil and depth buffers between framebuffers is possible using framebuffer objects but usually not with the window system–provided framebuffer. Stencil and depth buffers and their corresponding state are usually allocated implicitly with the window system–provided drawable surface and, therefore, cannot be shared between drawable surfaces. With application-created framebuffer objects, stencil and depth renderbuffers can be created independently and then associated with a framebuffer object by attaching these buffers to appropriate attachment points in multiple framebuffer objects, if desired.

Creating Framebuffer and Renderbuffer Objects

Creating framebuffer and renderbuffer objects is similar to how texture or vertex buffer objects are created in OpenGL ES 3.0.

The glGenRenderbuffers API call is used to allocate renderbuffer object names. This API is described next.

Image

glGenRenderbuffers allocates n renderbuffer object names and returns them in renderbuffers. The renderbuffer object names returned by glGenRenderbuffers are unsigned integer numbers other than 0. These names returned are marked in use but do not have any state associated with them. The value 0 is reserved by OpenGL ES and does not refer to a renderbuffer object. Applications trying to modify or query the buffer object state for renderbuffer object 0 will generate an appropriate error.

The glGenFramebuffers API call is used to allocate framebuffer object names. This API is described here.

Image

glGenFramebuffers allocates n framebuffer object names and returns them in ids. The framebuffer object names returned by glGenFramebuffers are unsigned integer numbers other than 0. The framebuffer names returned are marked in use but do not have any state associated with them. The value 0 is reserved by OpenGL ES and refers to the window system–provided framebuffer. Applications trying to modify or query the buffer object state for framebuffer object 0 will generate an appropriate error.

Using Renderbuffer Objects

In this section, we describe how to specify the data storage, format, and dimensions of the renderbuffer image. To specify this information for a specific renderbuffer object, we need to make this object the current renderbuffer object. The glBindRenderbuffer command is used to set the current renderbuffer object.

Image

Note that glGenRenderbuffers is not required to assign a renderbuffer object name before it is bound using glBindRenderbuffer. Although it is a good practice to call glGenRenderbuffers, many applications specify compile-time constants for their buffers. An application can specify an unused renderbuffer object name to glBindRenderbuffer. However, we do recommend that OpenGL ES applications call glGenRenderbuffers and use renderbuffer object names returned by glGenRenderbuffers instead of specifying their own buffer object names.

The first time the renderbuffer object name is bound by calling glBindRenderbuffer, the renderbuffer object is allocated with the appropriate default state. If this allocation is successful, the allocated object will become the newly bound renderbuffer object.

The following state and default values are associated with a renderbuffer object:

• Width and height in pixels—The default value is zero.

• Internal format—This describes the format of the pixels stored in the renderbuffer. It must be a color-, depth-, or stencil-renderable format.

• Color bit-depth—This is valid only if the internal format is a color-renderable format. The default value is zero.

• Depth bit-depth—This is valid only if the internal format is a depth-renderable format. The default value is zero.

• Stencil bit-depth—This is valid only if the internal format is a stencil-renderable format. The default value is zero.

glBindRenderbuffer can also be used to bind to an existing renderbuffer object (i.e., an object that has been assigned and used before and, therefore, has a valid state associated with it). No changes to the state of the newly bound renderbuffer object are made by the bind command.

Once a renderbuffer object is bound, we can specify the dimensions and format of the image stored in the renderbuffer. The glRenderbufferStorage command can be used for this purpose.

glRenderbufferStorage looks very similar to glTexImage2D, except that no image data is supplied. You can also create a multisample renderbuffer by using the glRenderbufferStorageMultisample command. glRenderbufferStorage is equivalent toglRenderStorageMultisample with samples set to zero. The width and height of the renderbuffer are specified in pixels and must be values that are smaller than the maximum renderbuffer size supported by the implementation. The minimum size value that must be supported by all OpenGL ES implementations is 1. The actual maximum size supported by the implementation can be queried using the following code:

GLint maxRenderbufferSize = 0;
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize);

Image

The internalformat argument specifies the format that the application would like to use to store pixels in the renderbuffer object. Table 12-1 lists the renderbuffer formats to store a color-renderable buffer, and Table 12-2 lists the formats to store a depth-renderable or stencil-renderable buffer.

The renderbuffer object can be attached to the color, depth, or stencil attachment of the framebuffer object without the renderbuffer’s storage format and dimensions being specified. The renderbuffer’s storage format and dimensions can be specified before or after the renderbuffer object has been attached to the framebuffer object. This information will, however, need to be correctly specified before the framebuffer object and renderbuffer attachment can be used for rendering.

Multisample Renderbuffers

Multisample renderbuffers enable the application to render to off-screen framebuffers with multisample anti-aliasing. The multisample renderbuffers cannot be directly bound to textures, but they can be resolved to single-sample textures using the newly introduced framebuffer blit (described later in this chapter).

As described in the previous section, to create a multisample renderbuffer, you use the glRenderbufferStorageMultisample API.

Renderbuffer Formats

Table 12-1 lists the renderbuffer formats to store a color-renderable buffer, and Table 12-2 lists the renderbuffer formats to store a depth-renderable or stencil-renderable buffer.

Image

Image

i denotes an integer; ui denotes an unsigned integer type.

Table 12-1 Renderbuffer Formats for Color-Renderable Buffer

Image

f denotes a float type.

Table 12-2 Renderbuffer Formats for Depth-Renderable and Stencil-Renderable Buffer

Using Framebuffer Objects

We describe how to use framebuffer objects to render to an off-screen buffer (i.e., renderbuffer) or to render to a texture. Before we can use a framebuffer object and specify its attachments, we need to make it the current framebuffer object. The glBindFramebuffer command is used to set the current framebuffer object.

Image

Note that glGenFramebuffers is not required to assign a framebuffer object name before it is bound using glBindFramebuffer. An application can specify an unused framebuffer object name to glBindFramebuffer. However, we do recommend that OpenGL ES applications call glGenFramebuffers and use framebuffer object names returned by glGenFramebuffers instead of specifying their own buffer object names.

On some OpenGL ES 3.0 implementations, the first time a framebuffer object name is bound by calling glBindFramebuffer, the framebuffer object is allocated with the appropriate default state. If the allocation is successful, this allocated object is bound as the current framebuffer object for the rendering context.

The following state is associated with a framebuffer object:

• Color attachment point—The attachment point for the color buffer.

• Depth attachment point—The attachment point for the depth buffer.

• Stencil attachment point—The attachment point for the stencil buffer.

• Framebuffer completeness status—Whether the framebuffer is in a complete state and can be rendered to.

For each attachment point, the following information is specified:

• Object type—Specifies the type of object that is associated with the attachment point. This can be GL_RENDERBUFFER if a renderbuffer object is attached or GL_TEXTURE if a texture object is attached. The default value is GL_NONE.

• Object name—Specifies the name of the object attached. This can be either the renderbuffer object name or the texture object name. The default value is 0.

• Texture level—If a texture object is attached, then this specifies the mip level of the texture associated with the attachment point. The default value is 0.

• Texture cubemap face—If a texture object is attached and the texture is a cubemap, then this specifies which one of the six cubemap faces is to be used as the attachment point. The default value is GL_TEXTURE_CUBE_MAP_POSITIVE_X.

• Texture layer—Specifies the 2D slice of the 3D texture to be used as the attachment point. The default value is 0.

glBindFramebuffer can also be used to bind to an existing framebuffer object (i.e., an object that has been assigned and used before and, therefore, has valid state associated with it). No changes are made to the state of the newly bound framebuffer object.

Once a framebuffer object has been bound, the color, depth, and stencil attachments of the currently bound framebuffer object can be set to a renderbuffer object or a texture. As shown in Figure 12-1, the color attachment can be set to a renderbuffer that stores color values, or to a mip level of a 2D texture or a cubemap face, or to a layer of a 2D array textures, or to a mip level of a 2D slice in a 3D texture. The depth attachment can be set to a renderbuffer that stores depth values or packed depth and stencil values, to a mip level of a 2D depth texture, or to a depth cubemap face. The stencil attachment must be set to a renderbuffer that stores stencil values or packed depth and stencil values.

Attaching a Renderbuffer as a Framebuffer Attachment

The glFramebufferRenderbuffer command is used to attach a renderbuffer object to a framebuffer attachment point.

Image

If glFramebufferRenderbuffer is called with renderbuffer not equal to zero, this renderbuffer object will be used as the new color, depth, or stencil attachment point as specified by the value of the attachment argument.

The attachment point’s state will be modified to

• Object type = GL_RENDERBUFFER

• Object name = renderbuffer

• Texture level and texture layer = 0

• Texture cubemap face = GL_NONE

The newly attached renderbuffer object’s state or contents of its buffer do not change.

If glFramebufferRenderbuffer is called with renderbuffer equal to zero, then the color, depth, or stencil buffer as specified by attachment is detached and reset to zero.

Attaching a 2D Texture as a Framebuffer Attachment

The glFramebufferTexture2D command is used to attach a mip level of a 2D texture or a cubemap face to a framebuffer attachment point. It can be used to attach a texture as a color, depth, or stencil attachment.

Image

If glFramebufferTexture2D is called with texture not equal to zero, then the color, depth, or stencil attachment will be set to texture. If glFramebufferTexture2D generates an error, no change is made to the state of the framebuffer.

The attachment point’s state will be modified to

• Object type = GL_TEXTURE

• Object name = texture

• Texture level = level

• Texture cubemap face = valid if the texture attachment is a cubemap and is one of the following values:

GL_TEXTURE_CUBE_MAP_POSITIVE_X

GL_TEXTURE_CUBE_MAP_POSITIVE_Y

GL_TEXTURE_CUBE_MAP_POSITIVE_Z

GL_TEXTURE_CUBE_MAP_NEGATIVE_X

GL_TEXTURE_CUBE_MAP_NEGATIVE_Y

GL_TEXTURE_CUBE_MAP_NEGATIVE_Z

• Texture layer = 0

The newly attached texture object’s state or contents of its image are not modified by glFramebufferTexture2D. Note that the texture object’s state and image can be modified after it has been attached to a framebuffer object.

If glFramebufferTexture2D is called with texture equal to zero, then the color, depth, or stencil attachment is detached and reset to zero.

Attaching an Image of a 3D Texture as a Framebuffer Attachment

The glFramebufferTextureLayer command is used to attach a 2D slice and a specific mip level of a 3D texture or a level of 2D array textures to a framebuffer attachment point. Refer to Chapter 9, “Texturing,” for a detailed description of how 3D textures work.

Image

The newly attached texture object’s state or contents of its image are not modified by glFramebufferTextureLayer. Note that the texture object’s state and image can be modified after it has been attached to a framebuffer object.

The attachment point’s state will be modified to

• Object type = GL_TEXTURE

• Object name = texture

• Texture level = level

• Texture cubemap face = GL_NONE

• Texture layer = 0

If glFramebufferTextureLayer is called with texture equal to zero, then the attachment is detached and reset to zero.

One interesting question arises: What happens if we are rendering into a texture and at the same time use this texture object as a texture in a fragment shader? Will the OpenGL ES implementation generate an error when such a situation arises? In some cases, it is possible for the OpenGL ES implementation to determine if a texture object is being used as a texture input and a framebuffer attachment into which we are currently drawing. glDrawArrays and glDrawElements could then generate an error. To ensure that glDrawArrays and glDrawElements can be executed as rapidly as possible, however, these checks are not performed. Instead of generating an error, in this case rendering results are undefined. It is the application’s responsibility to make sure that this situation does not occur.

Checking for Framebuffer Completeness

A framebuffer object needs to be defined as complete before it can be used as a rendering target. If the currently bound framebuffer object is not complete, OpenGL ES commands that draw primitives or read pixels will fail and generate an appropriate error that indicates the reason the framebuffer is incomplete.

The rules for a framebuffer object to be considered complete are as follows:

• Make sure that the color, depth, and stencil attachments are valid. A color attachment is valid if it is zero (i.e., there is no attachment) or if it is a color-renderable renderbuffer object or a texture object with one of the formats listed in Table 12-1. A depth attachment is valid if it is zero or is a depth-renderable renderbuffer object or a depth texture with one of the formats listed in Table 12-2 with depth buffer bits. A stencil attachment is valid if it is zero or is a stencil-renderable renderbuffer object with one of the formats listed in Table 12-2 with stencil buffer bits. There is a minimum of one valid attachment. A framebuffer is not complete if it has no attachments, as there is nothing to draw into or read from.

• Valid attachments associated with a framebuffer object must have the same width and height.

• If depth and stencil attachments exist, they must be the same image.

• The value of GL_RENDERBUFFER_SAMPLES is the same for all renderbuffer attachments. If the attachments are a combination of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES is zero.

The glCheckFramebufferStatus command can be used to verify that a framebuffer object is complete.

Image

glCheckFramebufferStatus returns zero if target is not equal to GL_FRAMEBUFFER. If target is equal to GL_FRAMEBUFFER, one of the following enums is returned:

• GL_FRAMEBUFFER_COMPLETE—Framebuffer is complete.

• GL_FRAMEBUFFER_UNDEFINED—If target is the default framebuffer but it does not exist.

• GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT—The framebuffer attachment points are not complete. This might be due to the fact that the required attachment is zero or is not a valid texture or renderbuffer object.

• GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT—No valid attachments in the framebuffer.

• GL_FRAMEBUFFER_UNSUPPORTED—The combination of internal formats used by attachments in the framebuffer results in a nonrenderable target.

• GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE—GL_RENDERBUFFER_SAMPLES is not the same for all renderbuffer attachments or GL_RENDERBUFFER_SAMPLES is non-zero when the attachments are a combination of renderbuffers and textures.

If the currently bound framebuffer object is not complete, attempts to use that object for reading and writing pixels will fail. In turn, calls to draw primitives, such as glDrawArrays and glDrawElements, and commands that read the framebuffer, such as glReadPixels,glCopyTeximage2D, glCopyTexSubImage2D, and glCopyTexSubImage3D, will generate a GL_INVALID_FRAMEBUFFER_OPERATION error.

Framebuffer Blits

Framebuffer blits allow for efficient copying of a rectangle of pixel values from one framebuffer (i.e., read framebuffer) to another framebuffer (i.e., draw framebuffer). One key application of framebuffer blits is to resolve a multisample renderbuffer to a texture (with a framebuffer object that has a texture bound for the color attachment).

You can perform this operation using the following command:

Image

Example 12-1 (as part of the Chapter_11/MRTs example) illustrates how to use framebuffer blits to copy four color buffers from a framebuffer object into four quadrants of the window for the default framebuffer.

Example 12-1 Copying Pixels Using Framebuffer Blits


void BlitTextures ( ESContext *esContext )
{
UserData *userData = esContext->userData;

// set the default framebuffer for writing
glBindFramebuffer ( GL_DRAW_FRAMEBUFFER,
defaultFramebuffer );

// set the fbo with four color attachments for reading
glBindFramebuffer ( GL_READ_FRAMEBUFFER, userData->fbo );

// Copy the output red buffer to lower-left quadrant
glReadBuffer ( GL_COLOR_ATTACHMENT0 );
glBlitFramebuffer ( 0, 0,
esContext->width, esContext->height,
0, 0,
esContext->width/2, esContext->height/2,
GL_COLOR_BUFFER_BIT, GL_LINEAR );

// Copy the output green buffer to lower-right quadrant
glReadBuffer ( GL_COLOR_ATTACHMENT1 );
glBlitFramebuffer ( 0, 0,
esContext->width, esContext->height,
esContext->width/2, 0,
esContext->width, esContext->height/2,
GL_COLOR_BUFFER_BIT, GL_LINEAR );

// Copy the output blue buffer to upper-left quadrant
glReadBuffer ( GL_COLOR_ATTACHMENT2 );
glBlitFramebuffer ( 0, 0,
esContext->width, esContext->height,
0, esContext->height/2,
esContext->width/2, esContext->height,
GL_COLOR_BUFFER_BIT, GL_LINEAR );

// Copy the output gray buffer to upper-right quadrant
glReadBuffer ( GL_COLOR_ATTACHMENT3 );
glBlitFramebuffer ( 0, 0,
esContext->width, esContext->height,
esContext->width/2, esContext->height/2,
esContext->width, esContext->height,
GL_COLOR_BUFFER_BIT, GL_LINEAR );
}


Framebuffer Invalidation

Framebuffer invalidation gives the application a mechanism to inform the driver that the contents of the framebuffer are no longer needed. This allows the driver to take several optimization steps: (1) skip unnecessary restoration of the contents of the tiles in tile-based rendering (TBR) architecture for further rendering to a framebuffer, (2) skip unnecessary data copying between GPUs in multi-GPU systems, or (3) skip flushing certain caches in some implementations to improve performance. This functionality is very important to achieve peak performance in many applications, especially those that perform significant amounts of off-screen rendering.

Let us review the design of TBR GPUs to understand why framebuffer invalidation is important for such GPUs. TBR GPUs are commonly employed on mobile devices to minimize the amount of data transferred between the GPU and system memory and thereby reduce one of the biggest consumers of power, memory bandwidth. This is done by adding a fast on-chip memory that can hold a small amount of pixel data. The framebuffer is then divided into many tiles. For each tile, primitives are rendered into the on-chip memory, and then the results are copied to the system memory once completed. Because only a minimal amount of data per pixel (the final pixel result) will be copied to the system memory, this approach saves memory bandwidth between the GPU and system memory.

With framebuffer invalidation, the GPU can remove contents of the framebuffer that are no longer required so as to reduce the amount of contents to be held per frame. In addition, the GPU may remove unnecessary data transfer from the on-chip memory to the system memory if the tile data is no longer valid. Because the memory bandwidth requirement between the GPU and system memory can be reduced significantly, this leads to reduced power consumption and improved performance.

The glInvalidateFramebuffer and glInvalidateSubFramebuffer commands are used to invalidate the entire framebuffer or a pixel subregion of the framebuffer.

Image

Deleting Framebuffer and Renderbuffer Objects

After the application has finished using renderbuffer objects, they can be deleted. Deleting renderbuffer and framebuffer objects is very similar to deleting texture objects.

Renderbuffer objects are deleted using the glDeleteRenderbuffers API.

Image

glDeleteRenderbuffers deletes the renderbuffer objects specified in renderbuffers. Once a renderbuffer object is deleted, it has no state associated with it and is marked as unused; it can then later be reused as a new renderbuffer object. When deleting a renderbuffer object that is also the currently bound renderbuffer object, the renderbuffer object is deleted and the current renderbuffer binding is reset to zero. If the renderbuffer object names specified in renderbuffers are invalid or zero, they are ignored (i.e., no error will be generated). Further, if the renderbuffer is attached to the currently bound framebuffer object, it is first detached from the framebuffer and only then deleted.

Framebuffer objects are deleted using the glDeleteFramebuffers API.

Image

glDeleteFramebuffers deletes the framebuffer objects specified in framebuffers. Once a framebuffer object is deleted, it has no state associated with it and is marked as unused; it can then later be reused as a new framebuffer object. When deleting a framebuffer object that is also the currently bound framebuffer object, the framebuffer object is deleted and the current framebuffer binding is reset to zero. If the framebuffer object names specified in framebuffers are invalid or zero, they are ignored and no error will be generated.

Deleting Renderbuffer Objects That Are Used as Framebuffer Attachments

What happens if a renderbuffer object being deleted is used as an attachment in a framebuffer object? If the renderbuffer object to be deleted is used as an attachment in the currently bound framebuffer object, glDeleteRenderbuffers will reset the attachment to zero. If the renderbuffer object to be deleted is used as an attachment in framebuffer objects that are not currently bound, then glDeleteRenderbuffers will not reset these attachments to zero. It is the responsibility of the application to detach these deleted renderbuffer objects from the appropriate framebuffer objects.

Reading Pixels and Framebuffer Objects

The glReadPixels command reads pixels from the color buffer and returns them in a user-allocated buffer. The color buffer that will be read from is the color buffer allocated by the window system–provided framebuffer or the color attachment of the currently bound framebuffer object. When a non-zero buffer object is bound to GL_PIXEL_PACK_BUFFER using glBindBuffer, the glReadPixels command can return immediately and invoke DMA transfer to read pixels from the framebuffer and write the data into the pixel buffer object.

Several combinations of format and type arguments in glReadPixels are supported: a format of GL_RGBA, GL_RGBA_INTEGER, or implementation-specific values returned by querying GL_IMPLEMENTATION_COLOR_READ_FORMAT; and a type of GL_UNSIGNED_BYTE,GL_UNSIGNED_INT, GL_INT, GL_FLOAT, or implementation-specific values returned by querying GL_IMPLEMENTATION_COLOR_READ_TYPE. The implementation-specific format and type returned will depend on the format and type of the currently attached color buffer. These values can change if the currently bound framebuffer changes. They must be queried whenever the currently bound framebuffer object changes to determine the correct implementation-specific format and type values that must be passed to glReadPixels.

Examples

Let’s now look at some examples that demonstrate how to use framebuffer objects. Example 12-2 demonstrates how to render to texture using framebuffer objects. In this example, we draw to a texture using a framebuffer object. We then use this texture to draw a quad to the window system–provided framebuffer (i.e., the screen). Figure 12-2 shows the generated image.

Image

Figure 12-2 Render to Color Texture

Example 12-2 Render to Texture


GLuint framebuffer;
GLuint depthRenderbuffer;
GLuint texture;
GLint texWidth = 256, texHeight = 256;
GLint maxRenderbufferSize;

glGetIntegerv ( GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize);

// check if GL_MAX_RENDERBUFFER_SIZE is >= texWidth and texHeight

if ( ( maxRenderbufferSize <= texWidth ) ||
( maxRenderbufferSize <= texHeight ) )
{
// cannot use framebuffer objects, as we need to create
// a depth buffer as a renderbuffer object
// return with appropriate error
}

// generate the framebuffer, renderbuffer, and texture object names
glGenFramebuffers ( l, &framebuffer );
glGenRenderbuffers ( l, &depthRenderbuffer );
glGenTextures ( l, &texture );

// bind texture and load the texture mip level 0
// texels are RGB565
// no texels need to be specified as we are going to draw into
// the texture
glBindTexture ( GL_TEXTURE_2D, texture );
glTexImage2D ( GL_TEXTURE_2D, O, GL_RGB, texWidth, texHeight, 0,
GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL );

glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR);

// bind renderbuffer and create a 16-bit depth buffer
// width and height of renderbuffer = width and height of
// the texture
glBindRenderbuffer ( GL_RENDERBUFFER, depthRenderbuffer );
glRenderbufferStorage ( GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
texWidth, texHeight );

// bind the framebuffer
glBindFramebuffer ( GL_FRAMEBUFFER, framebuffer );

// specify texture as color attachment
glFramebufferTexture2D ( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texture, 0 );

// specify depth_renderbuffer as depth attachment
glFramebufferRenderbuffer ( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, depthRenderbuffer);

// check for framebuffer complete
status = glCheckFramebufferStatus ( GL_FRAMEBUFFER );
if ( status == GL_FRAMEBUFFER_COMPLETE )
{
// render to texture using FBO
// clear color and depth buffer
glClearColor ( 0.0f, 0.0f, 0.0f, 1.0f );
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

// Load uniforms for vertex and fragment shaders
// used to render to FBO. The vertex shader is the
// ES 1.1 vertex shader described in Example 8-8 in
// Chapter 8. The fragment shader outputs the color
// computed by the vertex shader as fragment color and
// is described in Example 1-2 in Chapter 1.
set_fbo_texture_shader_and_uniforms( );

// drawing commands to the framebuffer object draw_teapot();

// render to window system-provided framebuffer
glBindFramebuffer ( GL_FRAMEBUFFER, 0 );
// Use texture to draw to window system-provided framebuffer.
// We draw a quad that is the size of the viewport.
//
// The vertex shader outputs the vertex position and texture
// coordinates passed as inputs.
//
// The fragment shader uses the texture coordinate to sample
// the texture and uses this as the per-fragment color value.
set_screen_shader_and_uniforms ( );
draw_screen_quad ( );
}

// clean up
glDeleteRenderbuffers ( l, &depthRenderbuffer );
glDeleteFramebuffers ( l, &framebuffer);
glDeleteTextures ( l, &texture );


In Example 12-2, we create the framebuffer, texture, and depthRenderbuffer objects using the appropriate glGen*** commands. The framebuffer object uses a color attachment that is a texture object (texture) and a depth attachment that is a renderbuffer object (depthRenderbuffer).

Before we create these objects, we query the maximum renderbuffer size (GL_MAX_RENDERBUFFER_SIZE) to verify that the maximum renderbuffer size supported by the implementation is less than or equal to the width and height of texture that will be used as a color attachment. This step ensures that we can create a depth renderbuffer successfully and use it as the depth attachment in framebuffer.

After the objects have been created, we call glBindTexture(texture) to make the texture the currently bound texture object. The texture mip level is then specified using glTexImage2D. Note that the pixels argument is NULL: We are rendering to the entire texture region, so there is no reason to specify any input data (this data will be overwritten).

The depthRenderbuffer object is bound using glBindRenderbuffer, and glRenderbufferStorage is called to allocate storage for a 16-bit depth buffer.

The framebuffer object is bound using glBindFramebuffer. texture is attached as a color attachment to framebuffer, and depthRenderbuffer is attached as a depth attachment to framebuffer.

We next check the framebuffer status to see if it is complete before we begin drawing into framebuffer. Once framebuffer rendering is complete, we reset the currently bound framebuffer to the window system–provided framebuffer by callingglBindFramebuffer(GL_FRAMEBUFFER, 0). We can now use texture, which was used as a render target in framebuffer, to draw to the window system–provided framebuffer.

In Example 12-2, the depth buffer attachment to framebuffer was a renderbuffer object. In Example 12-3, we consider how to use a depth texture as a depth buffer attachment to framebuffer. Applications can render to the depth texture used as a framebuffer attachment from the light source. The rendered depth texture can then be used as a shadow map to calculate the percentage in shadow for each fragment. Figure 12-3 shows the generated image.

Image

Figure 12-3 Render to Depth Texture

Example 12-3 Render to Depth Texture


#define COLOR_TEXTURE 0
#define DEPTH_TEXTURE 1

GLuint framebuffer;
GLuint textures[2];
GLint texWidth = 256, texHeight = 256;

// generate the framebuffer and texture object names
glGenFramebuffers ( l, &framebuffer );
glGenTextures ( 2, textures );

// bind color texture and load the texture mip level 0
// texels are RGB565
// no texels need to specified as we are going to draw into
// the texture

glBindTexture ( GL_TEXTURE_2D, textures[COLOR_TEXTURE] );
glTexImage2D ( GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0,
GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL );

glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR );

// bind depth texture and load the texture mip level 0
// no texels need to specified as we are going to draw into
// the texture
glBindTexture ( GL_TEXTURE_2D, textures[DEPTH_TEXTURE] );
glTexImage2D ( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, texWidth,
texHeight, 0, GL_DEPTH_COMPONENT,
GL_UNSIGNED_SHORT, NULL );

glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST );

// bind the framebuffer
glBindFramebuffer ( GL_FRAMEBUFFER, framebuffer );

// specify texture as color attachment
glFramebufferTexture2D ( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, textures[COLOR_TEXTURE],
0 );

// specify texture as depth attachment
glFramebufferTexture2D ( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D, textures[DEPTH_TEXTURE],
0 );

// check for framebuffer complete
status = glCheckFramebufferStatus ( GL_FRAMEBUFFER );
if ( status == GL_FRAMEBUFFER_COMPLETE )
{
// render to color and depth textures using FBO
// clear color and depth buffers
glClearColor ( 0.0f, 0.0f, 0.0f, 1.0f );
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

// Load uniforms for vertex and fragment shaders
// used to render to FBO. The vertex shader is the
// ES 1.1 vertex shader described in Example 8-8 in
// Chapter 8. The fragment shader outputs the color
// computed by vertex shader as fragment color and
// is described in Example 1-2 in Chapter 1.
set_fbo_texture_shader_and_uniforms( );

// drawing commands to the framebuffer object
draw_teapot( );

// render to window system-provided framebuffer
glBindFramebuffer ( GL_FRAMEBUFFER, 0 );

// Use depth texture to draw to window system framebuffer.
// We draw a quad that is the size of the viewport.
//
// The vertex shader outputs the vertex position and texture
// coordinates passed as inputs.
//
// The fragment shader uses the texture coordinate to sample
// the texture and uses this as the per-fragment color value.
set_screen_shader_and_uniforms( );
draw_screen_quad( );
}

// clean up
glDeleteFramebuffers ( l, &framebuffer );
glDeleteTextures ( 2, textures );



Note

The width and height of the off-screen renderbuffers do not have to be a power of 2.


Performance Tips and Tricks

Here, we discuss some performance tips that developers should carefully consider when using framebuffer objects:

• Avoid frequent switching between rendering to the window system–provided framebuffer and rendering to framebuffer objects. This is an issue for handheld OpenGL ES 3.0 implementations, as many of these implementations use a tile-based rendering architecture. With a tile-based rendering architecture, dedicated internal memory is used to store the color, depth, and stencil values for a tile (i.e., region) of the framebuffer. The internal memory is used as it is much more efficient in terms of power utilization, and it has better memory latency and bandwidth compared with going to external memory. After rendering to a tile is completed, the tile is written out to device (or system) memory. Every time you switch from one rendering target to another, the appropriate texture and renderbuffer attachments will need to be rendered, saved, and restored. This can become quite expensive. The best method would be to render to the appropriate framebuffers in the scene first, and then render to the window system–provided framebuffer, followed by execution of the eglSwapBuffers command to swap the display buffer.

• Don’t create and destroy framebuffer and renderbuffer objects (or any other large data objects for that matter) per frame.

• Try to avoid modifying textures (using glTexImage2D, glTexSubImage2D, glCopyTeximage2D, and so on) that are attachments to framebuffer objects used as rendering targets.

• Set the pixels argument in glTexImage2D and glTexImage3D to NULL if the entire texture image will be rendered, as the original data will not be used anyway. Use glInvalidateFramebuffer to clear the texture image before drawing to the texture if you expect the image to have any predefined pixel values in it.

• Share depth and stencil renderbuffers as attachments used by framebuffer objects wherever possible to keep the memory footprint requirement to a minimum. We recognize that this recommendation has limited use, as the width and height of these buffers have to be the same. In a future version of OpenGL ES, the rule that the width and height of various attachments of a framebuffer object must be equal might be relaxed, making sharing easier.

Summary

In this chapter, you learned about the use of framebuffer objects for rendering to off-screen surfaces. There are several uses of framebuffer objects, the most common of which is for rendering to a texture. You learned how to specify color, depth, and stencil attachments to a framebuffer object and how to copy and invalidate pixels in the framebuffer, and then saw some examples that demonstrated rendering to a framebuffer object. Understanding framebuffer objects is critical for implementing many advanced effects, such as reflections, shadow maps, and postprocessing. Next, you will learn about sync objects and fences—the mechanisms to synchronize the application and GPU execution.