james_lohr's picture

FBO Clear() bug

Hello,

Firstly, apologies for creating a new thread for this; however I was not able to figure out how to attach an image to my previous thread.

For the sake of completeness, let me again describe the problem I am having: I'm using FBOs as translucent overlays. Each frame I clear the FBOs using GL.Clear(ClearBufferMask.ColorBufferBit); before rendering to them, and then finally rendering both overlays to the screen as Texture2Ds. This has been working fine so far; however, when I have two of these overlays on the screen at once, the second FBO does not get cleared properly. Instead, GL.Clear(ClearBufferMask.ColorBufferBit); only clears a section of pixels that is the same size as the first FBO.

Let me illustrate what I mean. I've written a minimal application which recreates the problem, the full source code of which is attached below. Note, the issue is only present on my laptop - it all works fine on my PC. The application simply draws two rotating clock-hands to two FBOs, then turns these into textures and renders the textures to the screen.

The first image is how it looks on my PC (working correctly).

The second image is how it looks on my Laptop (not clearing the FBO properly). Note that only a small area (bottom left corner) of the second (large) FBO is getting cleared. This area appears to correspond to the first FBO that I'm clearing, almost as if the GL.Clear() call is not taking into account the new viewport size.

Here is the complete source:

Any help would be greatly appreciated. :)

[edit] - I've removed calls of PushAttrib/PopAttrib to eliminate it as the potential cause.

using System;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Audio;
using OpenTK.Audio.OpenAL;
using OpenTK.Input;
 
namespace FBOIssue
{
    class Game : GameWindow
    {
        public Game()
            : base(800, 600, GraphicsMode.Default)
        {
            this.WindowBorder = WindowBorder.Fixed;
            VSync = VSyncMode.On;
        }
 
        private ScreenOverlay smallOverlay, largeOverlay;
        private double waveT = 0;
 
        protected void SetDefaultViewport() { GL.Viewport(0, 0, Width, Height); }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            SetDefaultViewport();
            GL.MatrixMode(MatrixMode.Projection);
            GL.Ortho(0, 800, 600, 0, -1, 100);
            smallOverlay = new ScreenOverlay(128, 128);
            largeOverlay = new ScreenOverlay(512, 512);
            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
        }
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
            waveT += e.Time;
            if (Keyboard[Key.Escape])
                Exit();
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            GL.ClearColor(0f, 0f, 0.5f, 0f);
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
 
            smallOverlay.Begin();
                GL.ClearColor(0f, 0f, 0f, 0f);
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.Disable(EnableCap.Texture2D);
                GL.Color4(1.0f, 0.0f, 1.0f, 1.0f);
                GL.Begin(BeginMode.Lines);
                GL.Vertex2(64, 64);
                GL.Vertex2(64 + 64 * Math.Sin(waveT), 64 + 64 * Math.Cos(waveT));
                GL.End();
            smallOverlay.End();
 
            largeOverlay.Begin();
                GL.ClearColor(0f, 0f, 0f, 0f);
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.Disable(EnableCap.Texture2D);
                GL.Color4(1.0f, 1.0f, 0.0f, 1.0f);
                GL.Begin(BeginMode.Lines);
                GL.Vertex2(256, 256);
                GL.Vertex2(256 + 256 * Math.Sin(waveT),256 + 256 * Math.Cos(waveT));
                GL.End();
            largeOverlay.End();
            SetDefaultViewport();
 
            smallOverlay.Draw(0,128,0,128);
            largeOverlay.Draw(128, 128 + 512, 0, 512);
 
            SwapBuffers();
        }
 
        [STAThread]
        static void Main()
        {
            using (Game game = new Game())
            {
                game.Run(30.0);
            }
        }
 
 
        private class ScreenOverlay
        {
            protected int FboWidth;
            protected int FboHeight;
 
            protected uint FboHandle;
            protected uint ColorTexture;
 
            public ScreenOverlay(int width, int height)
            {
                FboWidth = width;
                FboHeight = height;
 
                GL.Enable(EnableCap.Texture2D);
 
                // 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.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
                GL.BindTexture(TextureTarget.Texture2D, 0);
 
                // 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);
 
            }
 
            /// <summary>
            /// Begin overlay rendering
            /// </summary>
            public virtual void Begin()
            {
                GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, FboHandle);
                GL.DrawBuffer((DrawBufferMode)FramebufferAttachment.ColorAttachment0Ext);
 
                //GL.PushAttrib(AttribMask.ViewportBit);
                GL.Viewport(0, 0, FboWidth, FboHeight);
 
                GL.MatrixMode(MatrixMode.Projection);
                GL.PushMatrix();
                GL.LoadIdentity();
 
                GL.Ortho(0, FboWidth, FboHeight, 0, -1, 100);
                GL.MatrixMode(MatrixMode.Modelview);
                GL.PushMatrix();
                GL.LoadIdentity();
            }
 
            /// <summary>
            /// End overlay rendering
            /// </summary>
            public virtual void End()
            {
                GL.PopMatrix(); //pop modelview matrix
                //GL.PopAttrib(); // restores GL.Viewport() parameters
                GL.BindTexture(TextureTarget.Texture2D, 0);
                GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // return to visible framebuffer
                GL.DrawBuffer(DrawBufferMode.Back);
 
                GL.MatrixMode(MatrixMode.Projection);
                GL.PopMatrix();  //pop projection matrix
                GL.MatrixMode(MatrixMode.Modelview);
            }
 
 
            public virtual void Draw(float left, float right, float top, float bottom)
            {
                GL.BindTexture(TextureTarget.Texture2D, ColorTexture);
                GL.Enable(EnableCap.Texture2D);
 
                GL.PushMatrix();
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(0f, 0f); GL.Vertex2(left, bottom);
                GL.TexCoord2(0f, 1f); GL.Vertex2(left, top);
                GL.TexCoord2(1f, 1f); GL.Vertex2(right, top);
                GL.TexCoord2(1f, 0f); GL.Vertex2(right, bottom);
                GL.End();
                GL.PopMatrix();
            }
 
            public virtual void Delete()
            {
                GL.DeleteTexture((int)ColorTexture);
                GL.Ext.DeleteFramebuffers(1, ref FboHandle);
            }
        }
 
    }
}
Inline Images

Comments

Comment viewing options

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

Just bumping this as I would really like to get to the bottom of the issue. :)

c2woody's picture

Are you aware that "bumping" threads is pretty annoying and usually provokes the contrary?

mOfl's picture

I just tested your sample code and it's displayed correctly on my 280M mobile card. What video card of which vendor are you using? Sounds like an incompatibility or a driver issue now. First thing you should try is updating your video card driver, then check if all the extensions you use are fully supported by your video card (you can do this either by checking it in your programm or manually by just looking through the OpenGL specs and what GL version they require).

Also, to answer the questions from the first thread: http://www.khronos.org/files/opengl-quick-reference-card.pdf this is the OpenGL 3.2 API quick reference card, i.e. it contains all GL and GLSL functions present in OpenGL 3.2. As OpenGL 3.2 (to be precise, it was 3.0) was the point where all the inefficient stuff was deprecated (but is still usable if you request a Compatibility Profile context), all deprecated stuff listed in this reference is marked with blue color. Everything that's blue is either fixed-function pipeline, inefficient, or both. The whole matrix and attribute stack thing belongs there, too.
Also, get functions such as GL.GetInteger are usually the slowest functions you can use, because stuff from the video memory has to be transfered back to main memory. Of course, a few Get calls per frame won't slow down your application, but it's always useful to keep it to a minimum. Why would you get the current viewport each frame in the first place? The viewport is a user setting and does as such belong to the CPU side of the application, i.e. you are supposed to store the viewport yourself in the application and modify the variables when the window is resized.

james_lohr's picture

Thanks a lot mOfl, that is very helpful.

>>Why would you get the current viewport each frame in the first place?

It is what the Textprinter currently does. The reason is that the both the TextPrinter and my font drawing library are separate libraries to OpenTK.

So like I said before, the options are:

1) Read the viewport value each frame within the library (so that the programmer doesn't have to worry when he/she changes the viewport using OpenTK, because the font drawing library automatically picks it up)
2) Require the programmer to explicitly notify the font drawing library upon a viewport change so that it can then re-read the new viewport settings

The old TextPrinter currently goes with 1). I'm currently going with 2) in my library because of the efficiency issue you have mentioned.

I think the ideal solution would be have OpenTk include a cached value for the last successfully set viewport change so that the viewport can be read without going away to the GPU.