dhakim's picture

Flickers + Garbage Resizing Form With Multiple GLControls

Hey all, I am once again trying to track down strange artifacts in my application. The artifact of the day is flickering and what appears to be frame buffer garbage on the edges of my GLControls while resizing the form. I don't remember any of this happening in XP, but has happened on both Windows 7 machines I have tried (with vastly different graphics cards). As such, I'm thinking this is due to something in Windows 7 or the way Windows 7 forms are drawn/interact with OpenTK.

Machine info: Brand new Windows 7 machine (Dell Vostro 430, EVGA NVidia GeForce GTX 460)

I've broken this up into several code samples that demonstrate the two (possibly related) problems I'm seeing.

Problem 1: Resize of a docked GLControl has slightly different functionality than resize of a docked Panel when double buffering style options of the containing form are enabled.

First its important to fully define standard functionality under Windows 7, which I definitely was not familiar with. Here's the best description I can create from the time I've spent playing around with resizing panels:

When you drag one corner, the window responds immediately, and fills the newly created space with the form's background color. Until appropriate graphics calls execute (ie SwapBuffers), that background color sits on screen.

Code sample demonstrating standard resize functionality with a single Panel:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
 
namespace SingleContextMultipleWindow
{
    public partial class Form4 : Form
    {
        Panel p;
        public Form4()
        {
            InitializeComponent();
//These styles make no difference, try turning them on and off!
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.ResizeRedraw, true);
 
            p = new Panel();
            p.Dock = DockStyle.Fill;
            p.Paint += new PaintEventHandler(p_Paint);
            this.Controls.Add(p);
 
            this.BackColor = Color.Blue;
        }
 
        void p_Paint(object sender, PaintEventArgs e)
        {
            Thread.Sleep(30); //Pretend youre doing something that takes time here, otherwise flicker is not noticeable.
            e.Graphics.Clear(Color.Red);            
        }
    }
}

You will note that there is a flicker in this standard functionality, but it is always the color of the form's background.

Lets look at it with GLControl
Code sample demonstrating standard resize functionality with a single GLControl:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using System.Threading;
 
namespace SingleContextMultipleWindow
{
    public partial class Form3 : Form
    {
        GLControl control;
        public Form3()
        {
            InitializeComponent();
 
            // Enable Double Buffering for Form
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.ResizeRedraw, true);
            this.BackColor = Color.Blue;
 
 
            control = new GLControl(GraphicsMode.Default);
            control.Dock = DockStyle.Fill;
            control.Paint += new PaintEventHandler(control_Paint);
            this.Controls.Add(control);
        }
 
        void control_Paint(object sender, PaintEventArgs e)
        {
            control.MakeCurrent();
            GL.ClearColor(Color.Red);
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.Flush();
            control.SwapBuffers();
        }
    }
}

This code will do the exact same thing as the panel, but will ignore the background color of the form, and instead fill the remaining space with black. However, if you comment out the SetStyle calls (which affect the form, not the GLControl), the remaining space, and thus the color of the flicker, changes to the expected Blue.

My two questions for this problem are:
A: Is there a way to prevent Windows 7 from drawing like this, that is, tell it to wait to draw the Window until it actually has something to draw, thus not flickering with any color.
B: Can I at the least set the flicker color for my GLControls inside of controls with the given double buffering control styles to something other than black somehow?

Problem 2 is very similar, and feels much more like it could be something in OpenTK

Problem 2: Resize of two GLControls in the same form exhibit similar resize behavior, except that extra space is sometimes background color and sometimes white, and if double buffering flags are on in surrounding controls, extra space is filled with what appears to be frame buffer garbage.

This time I'll dive straight into the two code samples, this time, two controls docked in the same form, we'll start with panels.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenTK.Platform;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using System.Threading;
using OpenTK;
 
namespace SingleContextMultipleWindow
{
    public partial class Form5 : Form
    {
        Panel p;
        Panel p2;
        public Form5()
        {
            InitializeComponent();
 
            // Enable Double Buffering for Form
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.ResizeRedraw, true);            
 
            this.BackColor = Color.Green;
            p = new Panel();
            p.Dock = DockStyle.Bottom;
            p.Paint += new PaintEventHandler(p_Paint);
 
            p2 = new Panel();
            p2.Dock = DockStyle.Right;
            p2.Paint += new PaintEventHandler(p2_Paint);
 
            this.Controls.Add(p);
            this.Controls.Add(p2);
        }
 
        void p_Paint(object sender, PaintEventArgs e)
        {
            Thread.Sleep(30);
            e.Graphics.Clear(Color.Red);
        }
 
        void p2_Paint(object sender, PaintEventArgs e)
        {
            Thread.Sleep(30);
            e.Graphics.Clear(Color.Blue);
        }
    }
}

This code produces a red panel on the bottom, and a blue panel on the right. When resized, there is green flickering, as green is the background of the form. The flickering of the red panel frustratingly covers up some of the blue panel, but as far as I can tell, people just deal with this. Also note that if you change the docks from Bottom and Right to Top and Left, the only flickering that occurs is identical to the single control case.

Now back to OpenTK GLControls:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenTK.Platform;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using System.Threading;
using OpenTK;
 
namespace SingleContextMultipleWindow
{
    public partial class Form2 : Form
    {
        GLControl gl;
        GLControl gl2;
        IGraphicsContext context;
        public Form2()
        {
            InitializeComponent();
 
            // Enable Double Buffering for Form
            //this.SetStyle(ControlStyles.UserPaint, true);
            //this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            //this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            //this.SetStyle(ControlStyles.ResizeRedraw, true);
 
 
            this.BackColor = Color.Green;
            gl = new GLControl(GraphicsMode.Default);
            gl.Dock = DockStyle.Bottom;
            gl.Paint += new PaintEventHandler(gl_Paint);
            gl.HandleCreated += new EventHandler(gl_HandleCreated);            
 
            gl2 = new GLControl(GraphicsMode.Default);
            gl2.Dock = DockStyle.Right;
            gl2.Paint += new PaintEventHandler(gl2_Paint);
 
            this.Controls.Add(gl);
            this.Controls.Add(gl2);
        }
 
        void gl_HandleCreated(object sender, EventArgs e)
        {
            this.context = gl.Context;
        }
 
        void gl_Paint(object sender, PaintEventArgs e)
        {
            try
            {
//                gl.MakeCurrent();
                context.MakeCurrent(gl.WindowInfo);
                GL.ClearColor(Color.Red);
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.Flush();
                context.SwapBuffers();
//                gl.SwapBuffers();
            }
            catch (Exception ex)
            {
                Console.WriteLine(context);
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
 
        void gl2_Paint(object sender, PaintEventArgs e)
        {
            try
            {
//                gl2.MakeCurrent();
                context.MakeCurrent(gl2.WindowInfo);
                GL.ClearColor(Color.Blue);
                GL.Clear(ClearBufferMask.ColorBufferBit);
                GL.Flush();
                context.SwapBuffers();
//                gl2.SwapBuffers();
            }
            catch (Exception ex)
            {
                Console.WriteLine(context);
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }
}

(This code draws the same basic scene using a single context for multiple GLControls. The code for doing it with a context per GLControl is commented out, and can be turned on, but makes no difference to the following statements)

Instead of drawing pure green flickers, it flickers green and white.

Further, if you uncomment the lines setting the containing form to be double buffered (specifically the AllPaintingInWmPaint line), it flickers black and white, with patterns that change depending on how the screen is moved.

Even further, minimizing the form, or shrinking it to be completely invisible then bringing it back out causes all white areas to disappear, and to use only green (or black with double buffering) All of those suggest to me that I'm looking at frame buffer garbage.

Finally, and this one still blows my mind a little bit: If you set the GLControl docks to be Top and Left instead of Bottom and Right, the only issue you'll notice is from Problem 1, black flicker instead of green.

That's all I've got. If there's interest, I can probably zip my testing project up instead of making people copy paste. I've got a strong hunch that the resize flickering I'm seeing is unavoidable and left up to the implementation, but if anybody knows a way to prevent it flickering, set the flickering color, or even just prevent it displaying random garbage I'd be very interested.

Failing any of that, I guess I'll petition for our software not to be arbitrarily resized by user while running, and/or suckitup and deal with flickering :(


Comments

Comment viewing options

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

Thanks for the detailed explanation. Could you please zip up and attach the whole project for these Forms? (The designer-generated files are missing and they can't be compiled without it).

As a rule of thumb, GLControl.DoubleBuffered should always be false. This property enables GDI/GDI+ double buffering which cannot handle OpenGL rendering - OpenGL double buffering is controlled by the GraphicsMode you pass to the GLControl constructor (it is enabled by default, which is why you use SwapBuffers()).

Does enabling/disabling GLControl.ResizeRedraw help?

dhakim's picture

Zip of solution is attached.
Form1 is two docked panels with background colors but no paints, exhibits no flickering
Form2 is two docked GLControls, and exhibits severe flickering on resize, as well as what appears to be left over frame buffer or back buffer.
Form3 is a single docked GLControl, and exhibits what I believe to be standard and accepted flickering, except when SetStyle calls on the containing form are set, at which point flicker color changes from background color to black. Not clear if this is a bug or my definition of standard simply includes implementation dependent behavior.
Form4 is a single docked Panel. This panel's paint is overridden to clear using e.Graphics. A Thread.sleep is used to force it to exhibit flickering, which it does. Flicker color is determined by containing form background color.
Form 5 is two docked Panels, and exhibits semi severe flickering on resize, but all flickering color is determined by back color of form.

--Edit: Change the form displayed by editing Program.cs

AttachmentSize
SingleContextMultipleWindow.zip18.32 KB
the Fiddler's picture

Thanks, let me check what happens on my system.

c2woody's picture

Did you experience the same behaviour on some class of systems (like on every win7 system you tried the flickering is similar, or on all nvidia systems etc.)?

It does not seem to create any unexpected overdrawings on the system here (vista64/older nvidia card).

dhakim's picture

Appears to be worst on Windows 7 machines, with both NVidia and Intel cards, seems to be far less noticeable on XP (NVidia and Intel cards as well). This could just be because the application I'm developing flickers colors closer to the interface colors on XP.

c2woody's picture

Ok talking only about your Form3 (GLControl) and Form4 (Panel), the behaviour seems consistent on the systems I tried. Don't know where/how double buffering would come into play, but if possible it should be disabled for relevant controls if possible (see The Fiddlers reply).

There's a fundamental difference between the two controls used in Form3 and Form4, namely the GLControl uses AllPaintingInWmPaint. Effectively Form3+GLControl tell that they handle everything on their own (paint routine) thus any delayed painting (and OpenGL is slightly delayed as well) will create artefacting on resizing, looks like "old buffer content" here.

The Panel draws its background (since the background is not explicitly set your form.BackColor affects the panel background color as well) even if the paint routine does not draw anything.

Try adding a e.Graphics.Clear(some color) call to control_Paint in Form3, as first statement in this function, which may be the effect you're looking for. I don't know if that's a good solution though, or if this can be achieved in a different way, too.

Btw. when testing, try to use additional geometry and not only Clear(color) calls since with those some effects are not immediately recognizable.

Biguri's picture

Is tehre any other solution for this?
I mean, I have the exact problem of Fomr 5 in my proyect, exactly with the same effects. Everytime I repaint a GLcontrol it flickers.
Has somebody found a solution?

kl_mallory's picture

I had this same problem when I used Windows System colors in the GL.ClearColor of my frame buffers. Especially on Intel cards. I fixed the problem by using a vector4 color. Try using an RGBA vector 4 version of the color. like this,

GL.ClearColor(255, 0, 0, 255);

All paid jobs absorb and degrade the mind -Aristotle