Framebuffer Object

From OpenGL Wiki
Jump to navigation Jump to 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 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.

Semantics[edit]

Framebuffer objects are a collection of attachments. To help explain lets 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 come from single mipmap levels 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 accessed from Shaders via various methods.
Renderbuffer
A renderbuffer is an object that contains a single image. Renderbuffers cannot be accessed by Shaders in any way. The only way to work with a renderbuffer, besides creating it, is to put it into an FBO.
Attach
To connect one object to another. This term is used across all of OpenGL, but FBOs make the most use of the concept. Attachment is different from binding. Objects are bound to the context; objects are attached to one another.
Attachment point
A named location within a framebuffer object that a framebuffer-attachable image or layered image can be attached to. Attachment points restrict the general kind of Image Format for images attached to them.
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.

Framebuffer Object Structure[edit]

As standard OpenGL Objects, FBOs have the usual glGenFramebuffers and glDeleteFramebuffers functions. 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_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.

The default framebuffer has buffer names like GL_FRONT, GL_BACK, GL_AUXi, GL_ACCUM, and so forth. FBOs do not use 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. Note that if no depth image is attached, Depth Testing will be disabled when rendering to this 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.

Attaching Images[edit]

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

You can attach images from most texture types to the framebuffer object. However, framebuffers are designed for 2D rendering. So there is a need to consider how different texture types map to framebuffer images. Remember that textures are a set of images. Textures can have mipmaps, and individual mipmap levels could contain one or more images.

The way different texture types map to framebuffer images is as follows:

  • Images in a 1D texture are considered 2D images with a vertical height of 1. Each individual image can be uniquely identified by a mipmap level​.
  • Images in a 2D texture are taken as normal. Each individual image can be uniquely identified by a mipmap level​.
  • A mipmap level of a 3D texture is considered to be a set of 2D images, with the number of these images being the extent of the Z coordinate for that mipmap level. Each integer value for the Z of a 3D texture mipmap level is a separate 2D layer. So each image in a 3D texture is uniquely identified by a layer​ and a mipmap level​. Recall that different mipmap levels in a 3D texture will have different counts of Z coordinates.
  • Rectangle Textures contain a single 2D image, which is identified by a level​ of 0.
  • Cubemap Textures contain 6 2D images per mipmap. Thus, each image in a cubemap texture can be uniquely identified by a face target​ and a mipmap level​. However, in some API functions, individual faces in a mipmap level are identified by a layer​ index instead of a target​.
  • Each mipmap level of 1D or 2D Array Textures contains a number of images, equal to the count images in the array. Thus, each individual image is uniquely identified by a layer​ (the array index) and a mipmap level​. For 1D array textures, each image has a height of 1. Unlike 3D textures, the layer​ doesn't change when going down the mipmap hierarchy.
  • Cubemap Array Textures work like 2D array textures, only with an image count 6 times the number of its layers. A 2D image in the cubemap array is identified by the layer-face index layer​ and a mipmap level​.
  • Buffer Textures cannot be attached to framebuffers.

The words level​, layer​, and target​ 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 (non-array) cubemap, you must use the Texture2D function, and the textarget​ must be one of the 6 targets for cubemap binding. When attaching an image from a cubemap array, you must use TextureLayer, with the layer​ being a layer-face.

Note: If OpenGL 4.5 or ARB_direct_state_access is available, then glFramebufferTextureLayer may take non-array cubemap textures. They are treated as though they were an array cubemap with one layer (so 6 layer-faces). This means that you never need to use the Texture2D​ or Texture1D​ functions for anything.
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.

Layered Images[edit]

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. Layered images are used with Layered Rendering, which sends different primitives to different layers within the framebuffer.

A single mipmap level of a 1D or 2D Array Texture can be attached as a layered image, where the number of layers is the array size. A single mipmap level of a 3D texture likewise can be attached as a layered image, where the number of layers is the depth of that particular mipmap level. Also, a mipmap level of a Cubemap Texture can be attached as 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
0 GL_TEXTURE_CUBE_MAP_POSITIVE_X
1 GL_TEXTURE_CUBE_MAP_NEGATIVE_X
2 GL_TEXTURE_CUBE_MAP_POSITIVE_Y
3 GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
4 GL_TEXTURE_CUBE_MAP_POSITIVE_Z
5 GL_TEXTURE_CUBE_MAP_NEGATIVE_Z

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 mipmap level, when can be attached as a layered image, has a specific number of layers. For 1D and 2D array textures, it is the number of layers in the texture as a whole. For 3D textures, this is the depth of that particular mipmap level. 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 attach 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.

Empty framebuffers[edit]

Empty Framebuffers
Core in version 4.6
Core since version 4.3
Core ARB extension ARB_framebuffer_no_attachments

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.

Framebuffer Completeness[edit]

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.

Attachment Completeness[edit]

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.

Completeness Rules[edit]

These are the rules for framebuffer completeness. The order of these rules matters.

  1. 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.
  2. All attachments must be attachment complete. (GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT when false).
  3. 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).
  4. Each draw buffer 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.
  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). Note that this test is not performed if OpenGL 4.1 or ARB_ES2_compatibility is available.
  6. All images must have the same number of multisample samples. All images must also use the same fixed sample layout setting. (GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE when false).
  7. 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.

Legacy Note: GL_FRAMEBUFFER_UNSUPPORTED was, in the days of EXT_framebuffer_object, much less forgiving. The specification didn't have a list of required image formats. Indeed, the only guarantee that the EXT_FBO spec made was that there was at least one combination of formats that implementations supported; it provided no hints as to what that combination might be. The core extension ARB_framebuffer_object does differ from the core specification in one crucial way: it uses the EXT wording for GL_FRAMEBUFFER_UNSUPPORTED. So if you're using 3.0, just use the required formats as above. If you're using ARB_framebuffer_object, then you should be concerned and do appropriate testing.

Feedback Loops[edit]

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.

Note: "image" here refers essentially to a mipmap level. So separate "images" within a single mipmap level of an array texture do not count as separate, so you will get UB if you try to write to one while reading from another. View textures can help resolve this, as you can get an array layer to count as a separate image.

Do not do this. What you will get is undefined behavior.

OpenGL OpenGL 4.5 or ARB_texture_barrier reduces the cases of feedback loops to just reading/writing from the same pixels, and even allows a limited ability to read/write the same pixels.

Pseudo Implementation[edit]

A pseudo implementation will make framebuffers much easier to understand.

struct Framebuffer {
  map<GLenum, Attachment> attachments;
  GLenum draw_buffers[] = { GL_COLOR_ATTACHMENT0, GL_NONE, GL_NONE, GL_NONE, .... };
  GLenum read_buffer = GL_COLOR_ATTACHMENT0;
}

Where Attachment can be one of

struct TextureAttachment: public Attachment {
  GLuint texture_id;
  GLenum texture_target;
  GLuint mip_level;
  GLuint layer;
};

struct RenderbufferAttachment: public Attachment {
  GLuint renderbuffer_id;
  Glenum renderbuffer_target;
};

Binding a framebuffer conceptually works like this

class GLContext {
  GLuint read_framebuffer_binding;
  GLuint draw_framebuffer_binding:
}

GLContext currentContext = ...

glBindFramebuffer(target, framebuffer_id) {
  switch (target) {
    case GL_READ_FRAMEBUFFER:
      currentContext.read_framebuffer_binding = framebuffer_id;
      break;
    case GL_DRAW_FRAMEBUFFER:
      currentContext.draw_framebuffer_binding = framebuffer_id;
      break;
    case GL_FRAMEBUFFER:
      currentContext.read_framebuffer_binding = framebuffer_id;
      currentContext.draw_framebuffer_binding = framebuffer_id;
      break;
  }
}

Attaching a texture or renderbuffer works like this

// Pseudo code
Framebuffer getFramebufferByTarget(GLenum target) {
  GLuint framebuffer_id;
  switch (target) {
    case GL_READ_FRAMEBUFFER:
      framebuffer_id = currentContext.read_framebuffer_binding;
    case GL_DRAW_FRAMEBUFFER:
    case GL_FRAMEBUFFER:
      framebuffer_id = currentContext.draw_framebuffer_binding;
  }
  Framebuffer framebuffer = getFramebufferById(framebuffer_id);
  return framebuffer;
}

glFramebufferTexture2D(GLenum target, GLenum attachmentPoint, GLenum textarget, GLuint texture, GLint mip_level) {
  Framebuffer frame_buffer = getFramebufferByTarget(target);
  frame_buffer.attachments.set(attachmentPoint, new TextureAttachment(textarget, texture, mip_level, 0));
}

glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint mip_level, GLint layer) {
  Framebuffer frame_buffer = getFramebufferByTarget(target);
  frame_buffer.attachments.set(attachment, new TextureAttachment(getTextureTargetFromTexture(texture), texture, mip_level, layer));
}

glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) {
  Framebuffer frame_buffer = getFramebufferByTarget(target);
  frame_buffer.attachments.set(attachment, new RenderbufferAttachment(renderbuffertarget, renderbuffer));
}

Setting the draw buffers and read buffer settings works like this

// Pseudo code
glDrawBuffers(GLsizei n, const GLenum *bufs) {
  Framebuffer frame_buffer = getFramebufferByTarget(GL_DRAW_FRAMEBUFFER);
  for (GLsizei i = 0; i < n; ++i) {
    frame_buffer.draw_buffers[i] = bufs[i];
  }
  for (GLsizei i = n; i < max_draw_buffers; ++i) {
    frame_buffer.draw_buffers[i] = GL_NONE;
  }
}

glReadBuffer(GLenum mode) {
  Framebuffer frame_buffer = getFramebufferByTarget(GL_READ_FRAMEBUFFER);
  frame_buffer.read_buffer = mode;
}

Conceptually it's super easy.

EXT_Framebuffer_object[edit]

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.

See Also[edit]

Reference[edit]