Tuesday, July 13, 2010

Double Buffering in C#

The standard class for graphical operations in Windows Forms - applications Graphis is unfortunately too slow for larger graphic outputs.
In applications, where a lot has to be drawn, the form starts blurring, the application freezes.
Of course, the with no doubt best and professional solution are external libraries like DirectX or XNA.
Today I do not want to go that far, but instead present you another useful technique for improving the performance: (Double) Buffering.
When drawing traditionally, each element is drawn for itself on the form, the user sees how the form is slowly built up. That means, it can even happen, that the form has to be redrawn during this step, the process slows down even further.
Therefor buffering is a commonly used improvement. In that, a so called Buffer is used, which temporarily saves the elements to be drawn.
The elements are then first drawn in the buffer, eventually the whole buffer is copied to the form.
So all elements are in fact drawn as one big picture, which makes the process much more fluent.
If the buffer is buffered itself, we speak of double buffering.
To demonstrate the effect of this technique, I write a little sample application.
After clicking the button, the program randomly draws circles and text on the form.
The first version uses only standard Graphics functions:
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.Drawing;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.Bounds = Screen.PrimaryScreen.Bounds;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Graphics G = this.CreateGraphics();
            Random Rnd = new Random();

            for (int i = 0; i < 1000; i++)
            {
                G.DrawEllipse(new System.Drawing.Pen(Color.Red), Rnd.Next(this.Width), Rnd.Next(this.Height), Rnd.Next(100), Rnd.Next(100));
            }

            for (int i = 0; i < 1000; i++)
            {
                G.DrawString("Test test test"new System.Drawing.Font("Arial", 12), new SolidBrush(Color.Green), new PointF(Rnd.Next(this.Width), Rnd.Next(this.Height)));
            }
        }
    }
}

Drawing takes its time, you see how the single elements are drawn one after another.

A possibility, to automatically active double buffering, is to add corresponding styles to the form.
The following code can for example be added to Form1_Load():
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

It enforces, that buffers are used. The process is thereby already speed up a lot.
But if one wants to have more control over the used buffers, they have to be implemented manually.
This last version of the application uses manually created buffer objects. The elements to be drawn are first drawn in the buffers and then copied on the form:

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;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // needed buffer objects
        BufferedGraphicsContext currentContext;
        BufferedGraphics myBuffer;

        private void Form1_Load(object sender, EventArgs e)
        {
            this.Bounds = Screen.PrimaryScreen.Bounds;
     
            // initialize buffer on the form
            currentContext = BufferedGraphicsManager.Current;
            myBuffer = currentContext.Allocate(this.CreateGraphics(), this.DisplayRectangle);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Random Rnd = new Random();

            // first draw elements to the buffer
            for (int i = 0; i < 1000; i++)
            {
                myBuffer.Graphics.DrawEllipse(new System.Drawing.Pen(Color.Red), Rnd.Next(this.Width), Rnd.Next(this.Height), Rnd.Next(100), Rnd.Next(100));
            }

            for (int i = 0; i < 1000; i++)
            {
                myBuffer.Graphics.DrawString("Test test test"new System.Drawing.Font("Arial", 12), new SolidBrush(Color.Green), new PointF(Rnd.Next(this.Width), Rnd.Next(this.Height)));
            }

            // then "render" the buffer, which means that the buffer is drawn to the form
            myBuffer.Render();
        }
    }
}

No comments:

Post a Comment