Having trouble with 2nd layer / nested FrameBufferObjects

I’m trying to render to a FrameBufferObject, and then render that to another FrameBufferObject. Since I couldn’t get it working in my game engine, I ran out to the net and cobbled together a really basic example in order to remove impacting variables. Even after this, I just can’t get it. See here:


    uint16 mDisplayHeight = 1000;
    uint16 mDisplayWidth = 1600;
    int32 mVideoFlags;

    // Init SDL
    SDL_Init(SDL_INIT_VIDEO);
    mVideoFlags  = SDL_OPENGL;          // Use OpenGL in SDL
    mVideoFlags |= SDL_HWPALETTE;       // Hardware enabled palette
    const SDL_VideoInfo* sdlVideoInfo = SDL_GetVideoInfo();
    if (sdlVideoInfo->hw_available)
        mVideoFlags |= SDL_HWSURFACE;   // Hardware enabled surfaces
    else
        mVideoFlags |= SDL_SWSURFACE;   // Software enabled surfaces
    if (sdlVideoInfo->blit_hw)
        mVideoFlags |= SDL_HWACCEL;     // Hardware enabled blitting
    SDL_SetVideoMode(mDisplayWidth, mDisplayHeight, 32, mVideoFlags);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);

    // Initialize OpenGL
    glewInit();
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DEPTH_TEST);

    /** First FBO - The Render One **/
    uint32 mFinalFBOTexID = 0;
    uint32 mFinalFBOID = 0;
    glGenFramebuffers(1, &mFinalFBOID);
    glBindFramebuffer(GL_FRAMEBUFFER, mFinalFBOID);
    glGenTextures(1, &mFinalFBOTexID);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFinalFBOTexID);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, mDisplayWidth,
                 mDisplayHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB,
                           mFinalFBOTexID, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    /****/

    /** Second FBO - Intermediate **/
    uint32 mSecondRenderTextureID = 0;
    uint32 mSecondRenderTextureFBO = 0;
    glGenFramebuffers(1, &mSecondRenderTextureFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, mSecondRenderTextureFBO);
    glGenTextures(1, &mSecondRenderTextureID);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mSecondRenderTextureID);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, 300,
                 300, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB,
                           mSecondRenderTextureID, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    /****/

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, mDisplayWidth, mDisplayHeight, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_TEXTURE_RECTANGLE_ARB);

    // Execution Loop
    while(true)
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glBindFramebuffer(GL_FRAMEBUFFER, mFinalFBOID); // X1
        //glBindFramebuffer(GL_FRAMEBUFFER, mSecondRenderTextureFBO); //  X2

        // Draw a quad
        glDisable(GL_TEXTURE_RECTANGLE_ARB);
        glColor3f(1.0f, 1.0f, 0.0f);
        glBegin(GL_QUADS);
            glVertex2i(10, 10);
            glVertex2i(50, 10);
            glVertex2i(50, 50);
            glVertex2i(10, 50);
        glEnd();
        glEnable(GL_TEXTURE_RECTANGLE_ARB);

        /* X2
        glBindFramebuffer(GL_FRAMEBUFFER, mFinalFBOID);
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mSecondRenderTextureID);

        glBegin(GL_QUADS);
            glTexCoord2i(0, 100);
            glVertex2i(0, 0);
            glTexCoord2i(100, 100);
            glVertex2i(100, 0);
            glTexCoord2i(100, 0);
            glVertex2i(100, 100);
            glTexCoord2i(0, 0);
            glVertex2i(0, 100);
        glEnd();
        */

        // Draw the 2D Layer on top
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glLoadIdentity();
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFinalFBOTexID);

        glBegin(GL_QUADS);
            glTexCoord2i(0, mDisplayHeight);
            glVertex2i(0, 0);
            glTexCoord2i(mDisplayWidth, mDisplayHeight);
            glVertex2i(mDisplayWidth, 0);
            glTexCoord2i(mDisplayWidth, 0);
            glVertex2i(mDisplayWidth, mDisplayHeight);
            glTexCoord2i(0, 0);
            glVertex2i(0, mDisplayHeight);
        glEnd();

        // Present the information
        SDL_GL_SwapBuffers();
    }

This code renders a yellow quad without any problems. But, if I uncomment the two X2 blocks and comment out the X1 block, I get a black screen. I can’t seem to figure out how to paint to and render that second FBO. What am I missing?

For x1 you have created mFinalFBOTexID and while rendering on default FBO you are binding this texture which is as per expected. But in x2, looks like you have created another texture “mSecondRenderTextureID” and while rendering on default FBO you are binding mFinalFBOTexID. i assume you dont have another texture rendered on x2 which is “mSecondRenderTextureID”, if this is so you must bind mFinalFBOTexID to x2 and not mSecondRenderTextureID.

One more, make sure that you have appropriate height width in glTexImage2D call for second FBO. Try changing it to as per glTeximage2D call for 1st FBO.

Good catch. After some tinkering and your direction, it now works. So that googlers can pull up a working example-- this works:

    uint16 mDisplayHeight = 1000;
    uint16 mDisplayWidth = 1600;
    int32 mVideoFlags;

    // Init SDL
    SDL_Init(SDL_INIT_VIDEO);
    mVideoFlags  = SDL_OPENGL;          // Use OpenGL in SDL
    mVideoFlags |= SDL_HWPALETTE;       // Hardware enabled palette
    const SDL_VideoInfo* sdlVideoInfo = SDL_GetVideoInfo();
    if (sdlVideoInfo->hw_available)
        mVideoFlags |= SDL_HWSURFACE;   // Hardware enabled surfaces
    else
        mVideoFlags |= SDL_SWSURFACE;   // Software enabled surfaces
    if (sdlVideoInfo->blit_hw)
        mVideoFlags |= SDL_HWACCEL;     // Hardware enabled blitting
    SDL_SetVideoMode(mDisplayWidth, mDisplayHeight, 32, mVideoFlags);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);

    // Initialize OpenGL
    glewInit();
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DEPTH_TEST);

    // Create the frame buffers and textures
    unsigned int mFrameBufferTexIDs[2];
    unsigned int mFrameBufferObjectIDs[2];
    for (int i = 0; i < 2; i++)
    {
        glGenFramebuffers(1, &mFrameBufferObjectIDs[i]);
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[i]);
        glGenTextures(1, &mFrameBufferTexIDs[i]);
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[i]);
        glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, mDisplayWidth, mDisplayHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[i], 0);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, mDisplayWidth, mDisplayHeight, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);

    // Framebuffer and texture arrays are as follows:
    // 0 : Final output (what I want to present to the screen)
    // 1 : Intermediate output (what I want to render to and then render THAT to the screen)
    bool renderToIntermediateFBO = true;

    while(true)
    {
        if (renderToIntermediateFBO == false)
        {
            glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[0]);

            // Draw a quad with no texture
            glDisable(GL_TEXTURE_RECTANGLE_ARB);
            glColor3f(1.0f, 1.0f, 0.0f);
            glBegin(GL_QUADS);
                glVertex2i(10, 10);
                glVertex2i(50, 10);
                glVertex2i(50, 50);
                glVertex2i(10, 50);
            glEnd();
            glEnable(GL_TEXTURE_RECTANGLE_ARB);
        }
        else
        {
            glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[1]);

            // Draw a quad with no texture
            glDisable(GL_TEXTURE_RECTANGLE_ARB);
            glColor3f(1.0f, 1.0f, 0.0f);
            glBegin(GL_QUADS);
                glVertex2i(10, 10);
                glVertex2i(50, 10);
                glVertex2i(50, 50);
                glVertex2i(10, 50);
            glEnd();
            glEnable(GL_TEXTURE_RECTANGLE_ARB);

            // Draw the intermediate FBO onto the final FBO
            glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[0]);
            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[1]);
            glBegin(GL_QUADS);
                glTexCoord2i(0, mDisplayHeight);
                glVertex2i(0, 0);
                glTexCoord2i(mDisplayWidth, mDisplayHeight);
                glVertex2i(mDisplayWidth, 0);
                glTexCoord2i(mDisplayWidth, 0);
                glVertex2i(mDisplayWidth, mDisplayHeight);
                glTexCoord2i(0, 0);
                glVertex2i(0, mDisplayHeight);
            glEnd();

            // Draw another untextured quad on the final FBO
            glDisable(GL_TEXTURE_RECTANGLE_ARB);
            glColor3f(0.0f, 1.0f, 1.0f);
            glBegin(GL_QUADS);
                glVertex2i(20, 20);
                glVertex2i(60, 20);
                glVertex2i(60, 60);
                glVertex2i(20, 60);
            glEnd();
            glEnable(GL_TEXTURE_RECTANGLE_ARB);
        }

        // Present the final information to the screen
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glLoadIdentity();
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[0]);
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
            glTexCoord2i(0, mDisplayHeight);
            glVertex2i(0, 0);
            glTexCoord2i(mDisplayWidth, mDisplayHeight);
            glVertex2i(mDisplayWidth, 0);
            glTexCoord2i(mDisplayWidth, 0);
            glVertex2i(mDisplayWidth, mDisplayHeight);
            glTexCoord2i(0, 0);
            glVertex2i(0, mDisplayHeight);
        glEnd();
        SDL_GL_SwapBuffers();
    }

Glad to hear that :slight_smile:

Hmm, well-- this does still leave me with a question. How do I perform this sort of logic with different sized framebufferobjects? If I flip the intermediate Texture to 512x512, I can’t get it to work anymore. Is there something else I need to be doing to make FBO’s different sizes? Is that even supported?

AFIK, you can’t have texture size greater than that of your attachments, plus all your attachments to FBO should be of equal sizes. But you can create texture of lower size than your FBOs so that at-least you wont get any fault during run but it will left you with loss of FBO data.
So i would suggest if you have textures of varying sizes, make sure that your FBOs have size greater than your largest possible value of texture size.

Conceptually, that makes perfect sense. But it looks like I’m running into the same problem I had originally when I apply it. See here for an example. I manually set the second (intermediate) FBO texture to be 512x512, and render within those paremeters. Now that quad isn’t rendering anymore. If I change mDisplayHeight and mDisplayWidth to both be 512, then it renders (since now both the final and the intermediate are 512x512). But as long as those are a higher value, all I get is black.

    unsigned short mDisplayHeight = 1000;
    unsigned short mDisplayWidth = 1600;
    int mVideoFlags;

    // Init SDL
    SDL_Init(SDL_INIT_VIDEO);
    mVideoFlags  = SDL_OPENGL;          // Use OpenGL in SDL
    mVideoFlags |= SDL_HWPALETTE;       // Hardware enabled palette
    const SDL_VideoInfo* sdlVideoInfo = SDL_GetVideoInfo();
    if (sdlVideoInfo->hw_available)
        mVideoFlags |= SDL_HWSURFACE;   // Hardware enabled surfaces
    else
        mVideoFlags |= SDL_SWSURFACE;   // Software enabled surfaces
    if (sdlVideoInfo->blit_hw)
        mVideoFlags |= SDL_HWACCEL;     // Hardware enabled blitting
    SDL_SetVideoMode(mDisplayWidth, mDisplayHeight, 32, mVideoFlags);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);

    // Initialize OpenGL
    glewInit();
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DEPTH_TEST);

    // Create the frame buffers and textures
    unsigned int mFrameBufferTexIDs[2];
    unsigned int mFrameBufferObjectIDs[2];

    glGenFramebuffers(1, &mFrameBufferObjectIDs[0]);
    glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[0]);
    glGenTextures(1, &mFrameBufferTexIDs[0]);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[0]);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, mDisplayWidth, mDisplayHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[0], 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    unsigned short intermediateTexWidth = 512;
    unsigned short intermediateTexHeight = 512;
    glGenFramebuffers(1, &mFrameBufferObjectIDs[1]);
    glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[1]);
    glGenTextures(1, &mFrameBufferTexIDs[1]);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[1]);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, intermediateTexWidth, intermediateTexHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[1], 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, mDisplayWidth, mDisplayHeight, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);

    // Framebuffer and texture arrays are as follows:
    // 0 : Final output (what I want to present to the screen)
    // 1 : Intermediate output (what I want to render to and then render THAT to the screen)
    while(true)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[1]);

        // Draw a quad with no texture
        glDisable(GL_TEXTURE_RECTANGLE_ARB);
        glColor3f(1.0f, 1.0f, 0.0f);
        glBegin(GL_QUADS);
            glVertex2i(10, 10);
            glVertex2i(50, 10);
            glVertex2i(50, 50);
            glVertex2i(10, 50);
        glEnd();
        glEnable(GL_TEXTURE_RECTANGLE_ARB);

        // Draw the intermediate FBO onto the final FBO
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[0]);
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[1]);
        glBegin(GL_QUADS);
            glTexCoord2i(0, intermediateTexHeight);
            glVertex2i(0, 0);
            glTexCoord2i(intermediateTexWidth, intermediateTexHeight);
            glVertex2i(intermediateTexWidth, 0);
            glTexCoord2i(intermediateTexWidth, 0);
            glVertex2i(intermediateTexWidth, intermediateTexHeight);
            glTexCoord2i(0, 0);
            glVertex2i(0, intermediateTexHeight);
        glEnd();

        // Present the final information to the screen
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glLoadIdentity();
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[0]);
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
            glTexCoord2i(0, mDisplayHeight);
            glVertex2i(0, 0);
            glTexCoord2i(mDisplayWidth, mDisplayHeight);
            glVertex2i(mDisplayWidth, 0);
            glTexCoord2i(mDisplayWidth, 0);
            glVertex2i(mDisplayWidth, mDisplayHeight);
            glTexCoord2i(0, 0);
            glVertex2i(0, mDisplayHeight);
        glEnd();
        SDL_GL_SwapBuffers();
    }

Perhaps I’m approaching the problem wrong. Should I be using a single FBO, but having two separate texture attachments? And if so, how do I change which texture attachment is active in a FBO at a time?

Okay… I figured it out. Here’s a working code example:

    unsigned short mDisplayHeight = 1000;
    unsigned short mDisplayWidth = 1600;
    int mVideoFlags;

    // Init SDL
    SDL_Init(SDL_INIT_VIDEO);
    mVideoFlags  = SDL_OPENGL;          // Use OpenGL in SDL
    mVideoFlags |= SDL_HWPALETTE;       // Hardware enabled palette
    const SDL_VideoInfo* sdlVideoInfo = SDL_GetVideoInfo();
    if (sdlVideoInfo->hw_available)
        mVideoFlags |= SDL_HWSURFACE;   // Hardware enabled surfaces
    else
        mVideoFlags |= SDL_SWSURFACE;   // Software enabled surfaces
    if (sdlVideoInfo->blit_hw)
        mVideoFlags |= SDL_HWACCEL;     // Hardware enabled blitting
    SDL_SetVideoMode(mDisplayWidth, mDisplayHeight, 32, mVideoFlags);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);

    // Initialize OpenGL
    glewInit();
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DEPTH_TEST);

    // Create the frame buffers and textures
    unsigned int mFrameBufferTexIDs[2];
    unsigned int mFrameBufferObjectIDs[2];

    glGenFramebuffers(1, &mFrameBufferObjectIDs[0]);
    glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[0]);
    glGenTextures(1, &mFrameBufferTexIDs[0]);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[0]);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, mDisplayWidth, mDisplayHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[0], 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    unsigned short intermediateTexWidth = 512;
    unsigned short intermediateTexHeight = 512;
    glGenFramebuffers(1, &mFrameBufferObjectIDs[1]);
    glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[1]);
    glGenTextures(1, &mFrameBufferTexIDs[1]);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[1]);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, intermediateTexWidth, intermediateTexHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[1], 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, mDisplayWidth, mDisplayHeight, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);

    // Framebuffer and texture arrays are as follows:
    // 0 : Final output (what I want to present to the screen)
    // 1 : Intermediate output (what I want to render to and then render THAT to the screen)
    while(true)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[1]);
        glLoadIdentity();
        glTranslated(0, mDisplayHeight-intermediateTexHeight, 0);

        // Draw a quad with no texture
        glDisable(GL_TEXTURE_RECTANGLE_ARB);
        glColor3f(1.0f, 1.0f, 0.0f);
        glBegin(GL_QUADS);
            glVertex2i(10, 10);
            glVertex2i(50, 10);
            glVertex2i(50, 50);
            glVertex2i(10, 50);
        glEnd();
        glEnable(GL_TEXTURE_RECTANGLE_ARB);

        // Draw the intermediate FBO onto the final FBO
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferObjectIDs[0]);
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[1]);
        glBegin(GL_QUADS);
            glTexCoord2i(0, 0);
            glVertex2i(0, 0);
            glTexCoord2i(intermediateTexWidth, 0);
            glVertex2i(intermediateTexWidth, 0);
            glTexCoord2i(intermediateTexWidth, intermediateTexHeight);
            glVertex2i(intermediateTexWidth, intermediateTexHeight);
            glTexCoord2i(0, intermediateTexHeight);
            glVertex2i(0, intermediateTexHeight);
        glEnd();

        // Present the final information to the screen
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glLoadIdentity();
        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFrameBufferTexIDs[0]);
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
            glTexCoord2i(0, 0);
            glVertex2i(0, 0);
            glTexCoord2i(mDisplayWidth, 0);
            glVertex2i(mDisplayWidth, 0);
            glTexCoord2i(mDisplayWidth, mDisplayHeight);
            glVertex2i(mDisplayWidth, mDisplayHeight);
            glTexCoord2i(0, mDisplayHeight);
            glVertex2i(0, mDisplayHeight);
        glEnd();

        SDL_GL_SwapBuffers();
    }

The problem was because of two factors. 1) I’m using ortho with a negative 1 on the top parameter to put the engine in a 2D top-left 0 coordinate space, and 2) FrameBufferObjects render with bottom-left being 0. So to fix it, I had to do the following:

  • Before drawing to the smaller intermediate FBO, glTranslated() y parameter was set to FinalHeight - IntermediateHeight
  • Invert the y rendering on the intermediate => final quad draw

Thanks for all of your help. I’m good now.