views:

717

answers:

4

My objective is to move visual elements drawn using methods of a Graphics object across a Form with no flicker or artifacting (in .NET 3.5). I can successfully achieve flickerless movement by either using automatic double buffering (setting the Form's DoubleBuffered property to true) or implementing a back-buffer myself. I am however struggling to find a way to use either method without clearly visible artifacts showing.

When using automatic double buffering, a page-tearing effect is evident. It looks as though the back-buffer is being slowly, progressively copied to the form, from top to bottom, over as many as three refreshes of my 60Hz LCD.

When I implement double buffering myself (see code block for details) it looks as though the back buffer is copied to the form fast enough for no page-tearing to occur. However another type of artifact sometimes appears. The code below, which allows you to move a blue rectangle on a white background left and right on the form using the arrow keys, should reproduce the effect as a series of horizontal white bands that sometimes appear on the left and right edges of the rectangle just as it is moved.

public partial class Form1 : Form {
    int x;
    Bitmap buffer;

    public Form1() {
        InitializeComponent();
        buffer = new Bitmap(Width, Height);
    }

    private void Form1_Paint(object sender, PaintEventArgs e) {
        Graphics g = Graphics.FromImage(buffer);
        g.Clear(Color.FromArgb(255, 255, 255));
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        g.FillRectangle(Brushes.Blue, x, 0, 100, 500);
        e.Graphics.DrawImageUnscaled(buffer, 0, 0);
    }

    protected override void OnPaintBackground(PaintEventArgs pevent) {
        //Don't allow the background to paint
    }

    private void Form1_KeyDown(object sender, KeyEventArgs e) {
        const int scrollSpeed = 20;
        if(e.KeyData == Keys.Left) {
            x -= scrollSpeed;
            Refresh();
        }
        else {
            if(e.KeyData == Keys.Right) {
                x += scrollSpeed;
                Refresh();
            }
        }
    }
}

if(others able to reproduce the effect) {
    Am I doing something wrong, perhaps triggering some
    kind of race condition in the painting code or is
    this just a behavior of the runtime that I have to
    live with?
}
else {
    Could it be a peculiarity of my display card or a
    bugged display driver?
}

+1  A: 

It may be important to know that graphics cards accelerating 2D operations usually accelerate GDI, not GDI+. I think that using a GDI bitmap and drawing it directly using a GDI methods (e.g. through p/invoke) would make things faster.

Lucero
Lucero is right; for best performance you'll want to BitBlt from the buffer to the screen.
overslacked
A: 

It actually looks "fine" to me when pressing a key and releasing (though if you hold the key down, there's a little bit of tearing that resolves itself on the next move).

I'm on Vista with an ATI x1300 and Intel e6400. Perhaps processing speed or your video card are an issue?

edit: The above being said, I'll agree with the others and say GDI+ is not the way to go if you're looking for fast. DirectX or using the Windows API may be the way to go.

Michael Todd
+1  A: 

Whenever I've done double-buffering, I've made sure to have no rendering code inside of the Paint event; the only code in the event was to copy from the buffer to the screen.

Try moving the rendering code into its own function, and then Invalidate the region (in this case the form):

RenderScene()
Invalidate()

See if that helps. For good measure, override OnResize to resize your buffer as well.

overslacked
You have it right there with Invalidate, Refresh is very expensive.
leppie
A: 

Besides what overslacked said to use Invalidate, you also dont need antialiasing for filling a rectangle with no transform applied.

leppie