Frame Buffer Objects (FBO)

Every OpenGL application has at least one framebuffer. You can think about it as a digital copy of what you see on your screen. But this also implies a restriction, you can only see 1 framebuffer at a time on-screen, but it might be desireable to have multiple off-screen framebuffers at your disposal. That's where Frame Buffer Object (FBO) comes into play.

Typical usage for FBO is High Dynamic Range Rendering, Shadow Mapping and other Render-To-Texture effects. Assuming the buzzwords tell you nothing, here's a quick example scenario. We have a Texture2D of a sign that has some wooden texture and reads "Blacksmith". However you intend to localize that sign, so the german version of your game reads "Schmiede" or the spanish version "herrería". What are the options? Manually create a new Texture for every sign in the game with a paint program? No. All you need is the wooden texture of the sign, without any letters. The texture can be used as target for Render-To-Texture, and OpenTK.Fonts provides you a way to write any text you like ontop of that texture.

The traditional approach to achieve that was rendering into the visible framebuffer, read the information back with GL.ReadPixels() or GL.CopyTexSubImage(), then clear the screen and proceed with rendering as usual. With FBO the copy can be avoided, since it allows to render directly into a texture.

Framebuffer Layout

A framebuffer consists of at least one of these buffers:

  • A depth buffer, with or without stencil mask. Typical depth buffer formats are 16, 24, 32 Bit integer or 32 Bit floating point. Stencil buffers can only be 8 Bits in size, a good mixed depth and stencil format is depth 24 Bit with stencil 8 Bit.
  • Color buffer(s) have 1-4 components, namely Red, Green, Blue and Alpha. Typical color buffer formats are RGBA8 (8 Bit per component, total 32 Bit) or RGBA16f (16 Bit floating point per component, total 64 Bit). This list is far from complete, there exist dozens of formats with different amount of components and precision per component.

Please note that there is no requirement to use both. It's perfectly valid to create a FBO which has only a color attachment but no depth attachment. Or the other way around.

When you use more than one buffer, some restrictions apply: All attachments to the FBO must have the same width and height. All color buffers must use the same format. For example, you cannot attach a RGBA8 and a RGBA16f Texture to the same FBO, even if they have the same width and height. OpenGL 3.0 does relax this restriction, by allowing attachments of different sizes to be attached. But only the smallest area covered by all attachments can be written to. The Extension EXTX_mixed_framebuffer_formats allows attaching different formats to the framebuffer, however this is reported to be very slow so far.

Renderbuffers

FBO allows 2 different types of targets to be attached to it. The already known textures 1D, 2D, Rectangle, 3D or Cube map, and a new type: the renderbuffer. They are not restricted to depth or stencil like the name might suggest, they can be used for color formats aswell.

Renderbuffer
Pro:

  • May support formats which are not available as texture.
  • Allows multisampling through Extensions.

Con:

  • Does not allow MipMaps, filter or wrapping mode to be specified.
  • Cannot be bound as sampler for shaders.
  • Restricted to be a 2-dimensional image.

Texture2D
Pro:

  • Allows MipMaps, filter and wrapping modes, just like every other texture.
  • Can be bound as sampler to a shader.

Con:

  • Might be slower than a renderbuffer, depending on hardware.

As a rule of thumb, do not use a renderbuffer if you plan to use the FBO attachments as textures at some later stage. The copy from renderbuffer into a texture will perform worse than rendering directly to the texture.

Let's take the wooden "Blacksmith" sign example from earlier again. The required end result must be a Texture2D, which can be bound when drawing the geometry of the sign. To give an overview about the options, here are some brief summaries how to accomplish obtaining the desired Texture2D:

  1. Using a visible framebuffer.
    The wooden texture is drawn into the framebuffer. Text is drawn. The final Texture is copied into a Texture2D. The screen must be cleared when done.
  2. Using a renderbuffer.
    The renderbuffer must be attached and the FBO bound. The wooden texture is drawn. Text is drawn. The final Texture is copied into a Texture2D. Either the renderbuffer is redundant now, or the screen must be cleared.
  3. Using a Texture2D.
    The texture must be attached and the FBO bound. Only Text is drawn. Done.

Example Setup

To give a concrete example how all this theory looks in practice: let's create a color texture, a depth renderbuffer and a FBO, then attach the texture and renderbuffer to the FBO. I'm assuming you read the VBO tutorial before this, so I'm not going through the purpose of handles, GL.Gen*, GL.Bind* and GL.Delete* functions again. Note that this is a C API and the same rule of binding 0 to disable or detach something is valid here too. E.g. GL.Ext.BindFramebuffer( FramebufferTarget.FramebufferExt, 0 ); will disable the last bound FBO and return rendering back to the visible window-system provided framebuffer.

const int FboWidth = 512;
const int FboHeight = 512;
 
uint FboHandle;
uint ColorTexture;
uint DepthRenderbuffer;
 
// Create Color Texture
GL.GenTextures( 1, out ColorTexture );
GL.BindTexture( TextureTarget.Texture2D, ColorTexture );
GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest );
GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Nearest );
GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.Clamp );
GL.TexParameter( TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.Clamp );
GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, FboWidth, FboHeight, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero );
 
// test for GL Error here (might be unsupported format)
 
GL.BindTexture( TextureTarget.Texture2D, 0 ); // prevent feedback, reading and writing to the same image is a bad idea
 
// Create Depth Renderbuffer
GL.Ext.GenRenderbuffers( 1, out DepthRenderbuffer );
GL.Ext.BindRenderbuffer( RenderbufferTarget.RenderbufferExt, DepthRenderbuffer );
GL.Ext.RenderbufferStorage(RenderbufferTarget.RenderbufferExt, (RenderbufferStorage)All.DepthComponent32, FboWidth, FboHeight);
 
// test for GL Error here (might be unsupported format)
 
// Create a FBO and attach the textures
GL.Ext.GenFramebuffers( 1, out FboHandle );
GL.Ext.BindFramebuffer( FramebufferTarget.FramebufferExt, FboHandle );
GL.Ext.FramebufferTexture2D( FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, ColorTexture, 0 );
GL.Ext.FramebufferRenderbuffer( FramebufferTarget.FramebufferExt, FramebufferAttachment.DepthAttachmentExt, RenderbufferTarget.RenderbufferExt, DepthRenderbuffer );
 
// now GL.Ext.CheckFramebufferStatus( FramebufferTarget.FramebufferExt ) can be called, check the end of this page for a snippet.
 
// since there's only 1 Color buffer attached this is not explicitly required
GL.DrawBuffer( (DrawBufferMode)FramebufferAttachment.ColorAttachment0Ext );
 
GL.PushAttrib( AttribMask.ViewportBit ); // stores GL.Viewport() parameters
GL.Viewport( 0, 0, FboWidth, FboHeight );
 
// render whatever your heart desires, when done ...
 
GL.PopAttrib( ); // restores GL.Viewport() parameters
GL.Ext.BindFramebuffer( FramebufferTarget.FramebufferExt, 0 ); // return to visible framebuffer
GL.DrawBuffer( DrawBufferMode.Back );

At this point you may bind the ColorTexture as source for drawing into the visible framebuffer, but be aware that it is still attached as target to the created FBO. That is only a problem if the FBO is bound again and the texture is used at the same time for being a FBO attachment target and the source of a texturing operation. This will cause feedback effects and is most likely not what you intended.
You may detach the ColorTexture from the FBO - the texture contents itself is not affected - by calling GL.Ext.FramebufferTexture2D() and attach a different target than ColorTexture to the ColorAttachment0 slot, for example simply 0. However the FBO would then be incomplete due to the missing color attachment, the best course of action is to detach the DepthRenderbuffer too and delete the renderbuffer and the FBO. Do not repeatedly attach and detach the same Texture if you want to update it every frame - just keep it attached to the FBO and make sure no feedback situation arises.

It is valid to attach the same texture or renderbuffer to multiple FBO at the same time. Example: you can avoid copies and save memory by attaching the same depth buffer to the FBOs, instead of creating multiple depth buffers and copy between them.

Special care has to be taken about 2 states that are always affected by FBOs: GL.Viewport() and GL.DrawBuffer(s). When switching from the visible framebuffer to a FBO, you should always set a proper viewport and drawbuffer. Switching framebuffer targets is such an expensive operation that the cost of the 2 extra calls to set up drawbuffers and viewport can be ignored. In the example setup above, the Viewport was stored and restored using GL.PushAttrib() and GL.PopAttrib(), but you may ofcourse specify it manually using GL.Viewport().

GL.DrawBuffer(s)

A FBO supports multiple color buffer attachments, if they have the same dimension and the same format. It is allowed to attach multiple color buffers - but only draw to one of them - by using the GL.DrawBuffer() command. Selecting multiple color buffers to write to is done with the GL.DrawBuffers() command, which expects an array like this:

DrawBuffersEnum[] bufs = new DrawBuffersEnum[2] { (DrawBuffersEnum)FramebufferAttachment.ColorAttachment0Ext, (DrawBuffersEnum)FramebufferAttachment.ColorAttachment1Ext }; // fugly, will be addressed in 0.9.2
 
GL.DrawBuffers( bufs.Length, bufs );

This code declares the color attachments 0 and 1 as buffers that can be written to. In practice this makes only sense if you're writing shaders with GLSL. (Look up "gl_FragData" for further info)

The exact number how many attachments are supported by the hardware must be queried through GL.GetInteger( GetPName.MaxColorAttachmentsExt, ... ) and the number of allowed Drawbuffers at the same time through GL.GetInteger( GetPName.MaxDrawBuffers, ... )

To select which buffer is affected by GL.ReadPixels() or GL.CopyTex*() calls, use GL.ReadBuffer().

Remarks

For the sake of simplicity, the window-system provided framebuffer was called "visible framebuffer". In reality this is only true if you requested a single-buffer context from OpenGL, but the more likely case is that you requested a double-buffered context. When using double buffers, the 'back' buffer is the one used for drawing and never visible on screen, the 'front' buffer is the one that is visible on screen. The two buffers are swapped with each other when you call this.SwapBuffers(), to avoid that slow computers show unfinished images on screen. FBO are not designed to be double buffered, because they are off-screen at all times.

The wooden "Blacksmith" sign example has some hidden complexities that are ignored for the sake of simplicity, such as that you may not want to print with standard fonts on the sign, that words in different languages can have different length or that it might be desireable to add an additional mask when writing the text to simulate the paint peeling off the sign.

This page does not cover all commands exposed by FBO. For a more detailed description you'll have to dig through the official specification.

These Extensions were merged into ARB_framebuffer_objects with OpenGL 3.0:

EXT_framebuffer_multisample allows the creation of renderbuffers with n samples per image.

EXT_framebuffer_blit allows to bind 2 FBO at the same time. One for reading and one for writing. Without this Extension the active framebuffer is used for both: reading and writing.

Snippet how to interpret the possible results from GL.CheckFramebufferStatus

        private bool CheckFboStatus( )
        {
            switch ( GL.Ext.CheckFramebufferStatus( FramebufferTarget.FramebufferExt ) )
            {
            case FramebufferErrorCode.FramebufferCompleteExt:
                {
                    Trace.WriteLine( "FBO: The framebuffer is complete and valid for rendering." );
                    return true;
                }
            case FramebufferErrorCode.FramebufferIncompleteAttachmentExt:
                {
                    Trace.WriteLine( "FBO: One or more attachment points are not framebuffer attachment complete. This could mean there’s no texture attached or the format isn’t renderable. For color textures this means the base format must be RGB or RGBA and for depth textures it must be a DEPTH_COMPONENT format. Other causes of this error are that the width or height is zero or the z-offset is out of range in case of render to volume." );
                    break;
                }
            case FramebufferErrorCode.FramebufferIncompleteMissingAttachmentExt:
                {
                    Trace.WriteLine( "FBO: There are no attachments." );
                    break;
                }
            /* case  FramebufferErrorCode.GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT: 
                 {
                     Trace.WriteLine("FBO: An object has been attached to more than one attachment point.");
                     break;
                 }*/
            case FramebufferErrorCode.FramebufferIncompleteDimensionsExt:
                {
                    Trace.WriteLine( "FBO: Attachments are of different size. All attachments must have the same width and height." );
                    break;
                }
            case FramebufferErrorCode.FramebufferIncompleteFormatsExt:
                {
                    Trace.WriteLine( "FBO: The color attachments have different format. All color attachments must have the same format." );
                    break;
                }
            case FramebufferErrorCode.FramebufferIncompleteDrawBufferExt:
                {
                    Trace.WriteLine( "FBO: An attachment point referenced by GL.DrawBuffers() doesn’t have an attachment." );
                    break;
                }
            case FramebufferErrorCode.FramebufferIncompleteReadBufferExt:
                {
                    Trace.WriteLine( "FBO: The attachment point referenced by GL.ReadBuffers() doesn’t have an attachment." );
                    break;
                }
            case FramebufferErrorCode.FramebufferUnsupportedExt:
                {
                    Trace.WriteLine( "FBO: This particular FBO configuration is not supported by the implementation." );
                    break;
                }
            default:
                {
                    Trace.WriteLine( "FBO: Status unknown. (yes, this is really bad.)" );
                    break;
                }
            }
            return false;
        }

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Eackz's picture

Thank you for this Information. It has really helped me :3