Framebuffer Objects are OpenGL Objects, which allow for the creation of user-defined Framebuffers. With them, one can render to non-Default Framebuffer locations, and thus render without disturbing the main screen.
Framebuffer objects are very complicated. As such, we need to explicitly define certain terminology.
Image: For the purposes of this article, an image is a single 2D array of pixels. It has a specific format for these pixels.
Layered Image: For the purposes of this article, a layered image is a sequence of images of a particular size and format. Layered images are a particular mipmap level of certain Texture types.
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.
Framebuffer-attachable layered image: Any layered 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 or layered 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
The target parameter for this object can take one of 3 values: GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER, or GL_DRAW_FRAMEBUFFER. The last two allow you to bind an FBO so that reading commands (glReadPixels, etc) and writing commands (all rendering commands) can happen to two different framebuffers. The GL_FRAMEBUFFER binding target simply sets both the read and the write to the same FBO.
When an FBO is bound to a target, the available buffer names. The default framebuffer has buffer names like GL_FRONT, GL_BACK, GL_AUXi, GL_ACCUM, and so forth. FBOs do not have these.
Instead, FBOs have a different set of image names. 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 8, so you are guaranteed to be able to have at least color attachments 0-7. These attachment points can only have images bound to them with color-renderable formats. All compressed image formats are not color-renderable, and thus cannot be attached to an FBO.
- GL_DEPTH_ATTACHMENT: This attachment point can only have images with depth formats bound to it. The image attached becomes the Depth Buffer for the FBO.
- GL_STENCIL_ATTACHMENT: This attachment point can only have images with stencil formats bound to it. The image attached becomes the stencil buffer for the FBO.
- GL_DEPTH_STENCIL_ATTACHMENT: This is shorthand for "both depth and stencil". The image attached becomes both the depth and stencil buffers.
- Note: If you use GL_DEPTH_STENCIL_ATTACHMENT, you should use a packed depth-stencil internal format for the texture or renderbuffer you are attaching.
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 height 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.
A Rectangle Texture has a single 2D image, and thus is identified by mipmap level 0.
Each mipmap level of a 3D texture is considered a set of 2D images, with the number of these being the extent of the Z 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 a 2D image. Thus, each image in a cubemap texture can be uniquely identified by a target and a mipmap level.
Each mipmap level of a 1D Array Textures contains a number of images, equal to the count images in the array. While these images are technically one-dimensional, they are promoted to 2D status for FBO purposes in the same way as a non-array 1D texture: by using a height of 1. Thus, each individual image is uniquely identified by a layer (the array index) and a mipmap level.
2D Array textures are much like 3D textures, except instead of the number of Z slices, it is the array count. Each 2D image in an array texture can be uniquely identified by a layer (the array index) and a mipmap level. Unlike 3D textures, the array count doesn't change when going down the mipmap hierarchy.
Cubemap array textures work like 2D array textures, only with 6 times the number of images. Thus a 2D image in the array is identified by the array layer (technically layer-face) and a mipmap level.
Buffer Textures work like 1D texture, only they have a single image, identified by mipmap level 0.
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.
A layered image, as previously defined, is an ordered sequence of images of a particular size. A number of different kinds of textures can be considered layered.
A single mipmap level of a 1D or 2D Array Texture is a layered image, where the number of layers is the array size. A single mipmap level of a 3D texture is likewise a layered image, where the number of layers is the depth of that particular mipmap level. Also, a mipmap level of a Cubemap Texture is a layered image. For cubemaps, you get exactly 6 layers, one for each face. And the order of the faces is the same as the order of the enumerators:
|Layer number||Cubemap face|
For cubemap arrays, the value that gl_Layer represents is the layer-face index. Thus it is the face within a layer, ordered as above. So if you want to render to the 3rd layer, +z face, you would set gl_Layer to (2 * 6) + 4, or 16.
Each texture, when taken as a layered image, has a specific number of layers. For array and 3D textures, this is the depth of the texture. For cubemaps, this is always exactly 6 layers: one per face. Cubemap arrays have 6 * the number of layers, which is the number of layer-faces.
To bind a mipmap level of a texture as a layered image, use the following command:
void glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
The parameters have the same meaning as above. Indeed, this function can replace many of the uses for glFramebufferTexture1D, 2D, or Layer, as long as you do not intend to attach specific layers of array textures, cubemaps, or 3D textures as regular, non-layered images. If the texture is one of these kinds of textures, then the given mipmap level will be attached as a layered image with the number of layers that the given texture has.
Layered image rendering is used with Geometry Shaders.
It is possible to render to a framebuffer object that has no attachments. Obviously none of the fragment shader outputs will be written to anywhere in this case, but rendering can otherwise proceed as normal. This is useful for using arbitrary reading and writing of image data from shaders, rather than writing to a bound framebuffer.
However, the rasterization of primitives is always based on the area and characteristics of the bound framebuffer. These characteristics (size, number of samples for multisample rendering, etc) would normally be defined by the attached images. If no images are attached, these characteristics must be defined in some other fashion.
The characteristics for an FBO with no attachments can be set with this function:
void glFramebufferParameteri(GLenum target, GLenum pname, GLint param);
target is the location where the framebuffer object is bound. To set the width, set pname to GL_FRAMEBUFFER_DEFAULT_WIDTH; to set the height, use GL_FRAMEBUFFER_DEFAULT_HEIGHT.
Layered framebuffers can be simulated by setting GL_FRAMEBUFFER_DEFAULT_LAYERS to a layer count other than 0. Multisample framebuffers can be simulated by setting GL_FRAMEBUFFER_DEFAULT_SAMPLES to a number of samples other than 0. Fixed multisample location can similarly be simulated by setting GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS to a non-zero value.
Note that rendering is only limited to these parameters if no images are attached to the FBO. If images are attached, then these parameters are ignored. You should only set these values if you intend to use the FBO without images.
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. 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 manually. However, using an incomplete FBO is an error, so it's always a good idea to check.
The return value is 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.
Each attachment point itself must be complete according to these rules. Empty attachments (attachments with no image attached) are complete by default. If an image is attached, 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 height of a 1D image is assumed to be 1). The width/height must also be less than GL_MAX_FRAMEBUFFER_WIDTH and GL_MAX_FRAMEBUFFER_HEIGHT respectively (if GL 4.3/ARB_framebuffer_no_attachments).
- The layer for 3D or array textures attachments is less than the depth of the texture. It must also be less than GL_MAX_FRAMEBUFFER_LAYERS (if GL 4.3/ARB_framebuffer_no_attachments).
- The number of samples must be less than GL_MAX_FRAMEBUFFER_SAMPLES (if GL 4.3/ARB_framebuffer_no_attachments).
- The image's format must match the attachment point's requirements, as defined above. Color-renderable formats for color attachments, etc.
These are the rules for framebuffer completeness. The order of these rules matters.
- If the target of glCheckFramebufferStatus references the Default Framebuffer (ie: FBO object number 0 is bound), 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. The rest of the rules apply when an FBO is bound.
- All attachments must be attachment complete. (GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT when false).
- There must be at least one image attached to the FBO, or if OpenGL 4.3 or ARB_framebuffer_no_attachments is available, the GL_FRAMEBUFFER_DEFAULT_WIDTH and GL_FRAMEBUFFER_DEFAULT_HEIGHT parameters of the framebuffer must both be non-zero. (GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT when false).
- Each draw buffers must either specify color attachment points that have images attached or must be GL_NONE. (GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER when false). Note that this test is not performed if OpenGL 4.1 or ARB_ES2_compatibility is available.
- 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). Note that this test is not performed if OpenGL 4.1 or ARB_ES2_compatibility is available.
- All images must have the same number of multisample samples. (GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE when false).
- If a layered image is attached to one attachment, then all attachments must be layered attachments. The attached layers do not have to have the same number of layers, nor do the layers have to come from the same kind of texture (a cubemap color texture can be paired with an array depth texture) (GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS when false).
Notice that there is no restriction based on size. The effective size of the FBO is the intersection of all of the sizes of the bound images (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.
There's one more rule that can trip you up:
- The implementation likes your combination of attached image formats. (GL_FRAMEBUFFER_UNSUPPORTED when false).
OpenGL allows implementations to state that they do not support some combination of image formats for the attached images; they do this by returning GL_FRAMEBUFFER_UNSUPPORTED when you attempt to use an unsupported format combination.
However, the OpenGL specification also requires that implementations support certain format combinations; if you use these, implementations are forbidden to return GL_FRAMEBUFFER_UNSUPPORTED. Implementations must allow any combination of color formats, so long as all of those color formats come from the required set of color formats.
These color formats can be combined with a depth attachment with any of the required depth formats. Stencil attachments can also be used, again with the required stencil formats, as well as the combined depth/stencil formats. However, implementations are only required to support both a depth and stencil attachment simultaneously if both attachments refer to the same image.
This means that if you want stencil with no depth, you can use one of the required stencil formats. If you want depth with no stencil, you can use one of the required depth formats. But if you want depth and stencil, you must use a depth/stencil format and the same image in that texture must be attached to both the depth and stencil attachments.
Staying within these limits means you won't see GL_FRAMEBUFFER_UNSUPPORTED. Going outside of these limits makes it entirely possible to get this incompleteness.
It is possible to bind a texture to an FBO, bind that same texture to a shader, and then try to render with it at the same time.
It is perfectly valid to bind one image from a texture to an FBO and then render with that texture, as long as you prevent yourself from sampling from that image. If you do try to read and write to the same image, you get undefined results. Meaning it may do what you want, the sampler may get old data, the sampler may get half old and half new data, or it may get garbage data. Any of these are possible outcomes.
Do not do this. What you will get is undefined behavior.
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.
- Category:Core API Ref Framebuffer Objects: Reference documentation for functions that deal with framebuffer objects and renderbuffer objects.