Framebuffer Object

From OpenGL Wiki
Revision as of 21:04, 23 August 2009 by Alfonse (talk | contribs) (Full article now online.)
Jump to: navigation, search
Framebuffer Object
Core in version 4.6
Core since version 3.0
Core ARB extension GL_ARB_framebuffer_object
EXT extension GL_EXT_framebuffer_object, GL_EXT_framebuffer_blit, GL_EXT_framebuffer_multisample, GL_EXT_packed_depth_stencil

Framebuffer Objects are a mechanism for rendering to images other than the default OpenGL Framebuffer. They allow you to render directly to textures, as well as blitting from one framebuffer to another.

Semantics

Framebuffer objects are very complicated. As such, we need to explicitly define our terminology, so that we are all talking about the same thing.

Image: For the purposes of this article, an image is a single 2D array of pixels. It has a specific format for the pixels.

Texture: For the purposes of this article, a texture is an object that contains some number of images, as defined above. All of the images have the same format, but they do not have to have the same size (different mip-maps, for example). Textures can be bound to shaders and rendered with.

Renderbuffer: A renderbuffer is an object that contains a single image. Renderbuffers cannot be bound to shaders or otherwise rendered with; they can only be attached to FBOs.

Framebuffer-attachable image: Any image, as previously described, that can be attached to a framebuffer object.

Attachment point: A named location within a framebuffer object that a framebuffer-attachable image can be attached to. Attachment points can have limitations on the format of the images attached there.

Attach: To connect one object to another. This is not limited to FBOs, but attaching is a big part of them. Attachment is different from binding. Objects are bound to the context; they are attached to each other.

Framebuffer Object Structure

As standard OpenGL Objects, FBOs have the usual glGen*/glDelete* creation style. As expected, it also has the usual glBindFramebuffer function, to bind an FBO to the context.

The target parameter for this object can take one of 3 values: GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER, or GL_WRITE_FRAMEBUFFER. The last two allow you to bind an FBO so that reading commands (glReadPixels, glCopyPixels, etc) and writing commands (any command of the form glDraw*) can happen to two different buffers. The GL_FRAMEBUFFER target simply sets both the read and the write to the same FBO.

When an FBO is bound to a target, the available surfaces change. The default framebuffer has buffers like GL_FRONT, GL_BACK, GL_AUXi, GL_ACCUM, and so forth. Each of these buffer types can have multiple images (the depth buffer of the back buffer is one image) and so forth. FBOs do not have these.

Instead, FBOs have a different set of images. Each FBO image represents an attachment point, a location in the FBO where an image can be attached. FBOs have the following attachment points:

  • GL_COLOR_ATTACHMENTi: These are an implementation-dependent number of attachment points. You can query GL_MAX_COLOR_ATTACHMENTS to determine the number of color attachments that an implementation will allow. The minimum value for this is 1, so you are guaranteed to be able to have at least color attachment 0. These attachment points can only have images bound to them with color formats.
  • GL_DEPTH_ATTACHMENT: This attachment point can only have images with depth formats bound to it.
  • GL_STENCIL_ATTACHMENT: This attachment point can only have images with stencil formats bound to it.
  • GL_DEPTH_STENCIL_ATTACHMENT: This is shorthand for "both depth and stencil".
Note: If you use GL_DEPTH_STENCIL_ATTACHMENT, you should use the GL_PACKED_DEPTH_STENCIL internal format for the texture or renderbuffer you are attaching.

Attaching Images

Now that we know where images can be attached to FBOs, we can start talking about how to actually attach images to these. Of course, in order to attach images to an FBO, we must first bind the FBO to the context.

You can attach images from any kind of texture to the framebuffer object.

Remember that textures are a set of images. Textures can have mipmaps; thus, each individual mipmap level can contain one or more images.

A 1D texture contains 2D images that have the vertical size of 1. Each individual image can be uniquely identified by a mipmap level.

A 2D texture contains 2D images. Each individual image can be uniquely identified by a mipmap level.

Each mipmap level of a 3D texture is considered a set of 2D textures, with the number of these being the extent of the W coordinate. Each integer value for the depth of a 3D texture mipmap level is a layer. So each image in a 3D texture is uniquely identified by a layer and a mipmap level.

Cubemaps contain 6 targets, each of which is equivalent to a 2D texture. Thus, each image in a cubemap texture can be uniquely identified by a target and a mipmap level.

Array textures are much like 3D textures. Each image in an array texture can be uniquely identified by a layer (the array index) and a mipmap level.

The highlighted words above are significant, as they match the parameters of the following functions used for attaching textures:

 void glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
 void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
 void glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);

The target parameter here is the same as the one for bind. However, GL_FRAMEBUFFER doesn't mean both read and draw (as that would make no sense); instead, it is the same as GL_DRAW_FRAMEBUFFER. The attachment parameter is one of the above attachment points.

The texture argument is the texture object name you want to attach from. If you pass zero as texture, this has the effect of clearing the attachment for this attachment, regardless of what kind of image was attached there.

Because texture objects can hold multiple images, you must specify exactly which image to attach to this attachment point. The parameters match their above definitions, with the exception of textarget.

When attaching a non-cubemap, textarget should be the proper texture type: GL_TEXTURE_1D, GL_TEXTURE_2D_MULTISAMPLE, etc. When attaching a cubemap, you must use the Texture2D function, and the textarget must be one of the 6 targets for cubemap binding.

Legacy Note: There is a function, glFramebufferTexture3D, specifically for 3D textures. However, you shouldn't bother with it, as the TextureLayer function can do everything it can and more.

Renderbuffers can also be attached to FBOs. Indeed, this is the only way to use them besides just creating the storage for them.

Once you have created a renderbuffer object and made storage for it (given a size and format), you can attach it to an FBO with this function:

  void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);

The parameters work mostly the same as with texture attachment. The renderbuffertarget param must be GL_RENDERBUFFER. The renderbuffer parameter is the renderbuffer object's name.

Framebuffer Completeness

Each attachment point in a FBO has specific restrictions on the format of images that can be attached to it. However, it is not an immediate GL error to attach an image to an attachment point that doesn't support that format. However, it is an error to try to use an FBO that has been improperly set up. There are also a number of other issues with regard to sizes of images and so forth that must be detected in order to be able to safely use the FBO.

An FBO that is valid for use is said to be "framebuffer complete". To test framebuffer completeness, call this function:

 GLenum glCheckFramebufferStatus(GLenum target);

You are not required to call this. However, using an incomplete FBO is an error.

The return value is either GL_FRAMEBUFFER_COMPLETE if the FBO can be used. If it is something else, then there is a problem. Below are the rules for completeness, and the associated return values you will receive if they are not followed.

Attachment Completeness

Each attachment has its own completeness rules. Empty attachments are complete by default. Otherwise, it must adhere to the following rules:

  • The source object for the image still exists and has the same type it was attached with.
  • The image has a non-zero width and height.
  • The layer for 3D texture attachments is less than the depth of the texture.
  • The image's format must match the attachment point's requirements, as defined above.

Completeness Rules

These are the rules for framebuffer completeness. Order matters.

  1. If the target of glCheckFramebufferStatus is the default framebuffer (FBO object number 0), and the default framebuffer does not exist, then you will get GL_FRAMEBUFFER_UNDEFINED. If the default framebuffer exists, then you always get GL_FRAMEBUFFER_COMPLETE.
  2. All attachments must be attachment complete, as defined above. (GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT when false).
  3. There must be at least one image attached to the FBO. (GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT when false).
  4. All draw buffers (see below) must specify attachment points that have images attached. (GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER when false).
  5. If the read buffer is set, then it must specify an attachment point that has an image attached. (GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER when false).
  6. All images must have the same number of multisample samples. (GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE when false).

Notice that there is no restriction based on size. The effective size of the FBO is thus the intersection of all of the sizes of the bound image (ie: the smallest in each dimension).

These rules are all code-based. If you ever get any of these values from glCheckFramebufferStatus, it is because your program has done something wrong in setting up the FBO. Each one has a specific remedy for it.

Unfortunately, there's one more rule:

  • The implementation likes your combination of attached images. (GL_FRAMEBUFFER_UNSUPPORTED when false).

This is not something that you can catch in your code. This is an implementation-defined limitation. To fix the problem... try using different image formats. The only thing the specification guarantees is that an implementation must provide one combination of formats that will work.

However, for practical purposes, this doesn't come up. You should still check, but so long as you stick to RGB-style color formats for render targets, you should be fine.

Multiple Render Targets

Modern shaders allow the user to render to multiple render targets simultaneously. To facilitate this, FBOs (and the default framebuffer) have a mapping that allows the user to define which fragment shader outputs go to which buffers. The way this works is somewhat complicated.

When linking a fragment shader, the user will assign each fragment shader output variable to a number, between 0 and GL_MAX_DRAW_BUFFERS. The draw buffers list in the FBO (or the default framebuffer) is used to map between the values set into the fragment shader and the attachment names in the FBO (or buffers in the default framebuffer).

For example, let's say your fragment shader defines the following outputs:

 out vec4 mainColor;
 out vec2 subsideraryInfo;

When you link your shader, you use glBindFragDataLocation to assign 0 to mainColor and 1 to subsideraryInfo.

It is up to the draw buffers state in the FBO to state where these get rendered to. To set this mapping, you use this function:

  void glDrawBuffers( GLsizei n, const GLenum *bufs );

This function sets up the entire mapping table in one shot. The indices in the list correspond to the values set with glBindFragDataLocation, so the list can only be as large as GL_MAX_DRAW_BUFFERS. The entries in the bufs array are enumerators referring to buffer names in the framebuffer.

When the default framebuffer is active, these enumerators are from the list of the default framebuffer buffer names. GL_AUXi, GL_BACK_LEFT, and so on. When an FBO is active (in the GL_DRAW_FRAMEBUFFER slot), these enumerators are GL_COLOR_ATTACHMENTi values (less than GL_MAX_COLOR_ATTACHMENTS, of course). In either case, an entry in the list can be GL_NONE, which means that the output (if the shader outputs a value for it at all) is discarded.

If you are only setting up one draw buffer, you may use glDrawBuffer. It takes one enumeration value and sets the fragment data location 0 to draw to that buffer. All other fragment data location values are set to GL_NONE.

Framebuffer Bliting

The reason for the separation of GL_DRAW_FRAMEBUFFER and GL_READ_FRAMEBUFFER bindings is to allow data in one buffer to be blitted to another buffer.

A blit operation is a special form of copy operation; it copies a rectangular area of pixels from one framebuffer to another. It is not the same as a simple glCopyPixels, as it has some very specific properties with regard to multisampling.

Performing a blit between framebuffers is quite simple. You bind the source framebuffer to GL_READ_FRAMEBUFFER, then bind the destination framebuffer to GL_DRAW_FRAMEBUFFER. After that, you call this function:

 void glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
   GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
   GLbitfield mask, GLenum filter);

The pixels in the rectangular area specified by the src values are copied to the rectangular area specified by the dst values. The mask parameter is a bitfield that specifies which kinds of buffers you want copied: GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT, or some combination. The filter parameter specifies how you want filtering performed if the two rectangles are not the same size.

Now one thing to keep in mind is this: blit operations only read from the color buffer specified by glReadBuffer and will only write to the color buffers specified by glDrawBuffers</codes>. If multiple draw buffers are specified, then multiple color buffers are updated. This assumes that mask included the color buffer. The depth and stencil buffers of the source framebuffers are blitted to the destination if the mask specifies them.

The glReadBuffer state is stored with the FBO/default framebuffer, just like the glDrawBuffers state.

Note that it is perfectly valid to read from the default framebuffer and write to an FBO, or vice-versa.

Multisampling Considerations

Multisampling is supported with the default framebuffer (through WGL/GLX_multisample) and/or FBOs (through multisampled renderbuffers or textures, where supported).

As explained in the article on Multisampling, a multisampled buffer must be resolved into a single sample before it can be displayed. Normally, this resolving operation is automatic, occurring during framebuffer swapping (though reading from the framebuffer can cause it to happen anyway).

Each FBO or framebuffer has a specific number of multisample samples (remember: an FBO cannot be framebuffer-complete if all of the attached images do not have the same number of samples). When you blit between two FBOs with the same number of samples, the copy is done directly; the destination buffer gets the same information the source had.

It is an error to blit between buffers with different numbers of samples, unless one of them has zero samples. You get this by not attaching multisampled images to that FBO, or not using multisampled default framebuffers.

In this special case, two things can happen. If the read framebuffer is the one with zero samples, then the draw buffer has all of its samples per-pixel replaced with the values from the read framebuffer. However, if the draw framebuffer is the one with zero samples, then it causes the multisampled framebuffer to have its multisamples resolved into a single sample per pixel. This explicit resolution is very useful when dealing with multisampled buffers.

As with all multisample behavior, none of this works at all unless glEnable(GL_MULTISAMPLE) is in effect.

EXT_Framebuffer_object

The original form of FBOs was this extension. It lacked quite a bit of the above functionality, which later extensions granted. The biggest difference is that it has more hard-coded restrictions on framebuffer completeness. All of the images have to be the same size in the EXT spec, for example. Some of these limitations were hardware-based. So there may be hardware that supports EXT_FBO and not ARB_FBO, even thought they support things like EXT_FBO_blit and other parts of ARB_FBO.