Поделиться через


Looking at double buffering and the new BufferedGraphics classes

I started spending some time looking at double buffering with Windows Forms a little while back and noticed that in the 2.0 framework there are a couple of new BufferedGraphics classes.  I toyed around with them a bit and got them to work, but didn’t really understand what was going on.  Looking into things again the past few days I found the available material somewhat lacking, so I did some investigation using Reflector and thought I’d share what I’ve found.

[What is double buffering?  It’s drawing on an off-screen bitmap then copying it onto the display to help avoid redraw flickering issues.]

Double buffering will be handled automatically for you if the OptimizedDoubleBuffer style is set to true through SetStyle().   MSDN: Double Buffered Graphics.

[Double buffering will also be done if the DoubleBuffer, UserPaint, and AllPaintingInWmPaint styles are all set to true.  Setting the DoubleBuffered property to true is another way to set the OptimizedDoubleBuffer style to true (as well as AllPaintingInWmPaint, but oddly it doesn’t turn off AllPaintingInWmPaint when set to false).  As of Beta2 the documentation says that DoubleBuffer style is obsolete, but it is still checked in the code.  (I’ve seen mention that double buffering is on by default in the 2.0 framework, but I haven’t seen this to be true in Beta2.)]

If you wish to manually manage double buffering you can do so using the new BufferedGraphics classes.   MSDN: How to: Manually Manage Buffered Graphics.  There are three key classes that you need to be familiar with:

  1. BufferedGraphics.
  2. BufferedGraphicsContext.
  3. BufferedGraphicsManager.

These classes are what Windows Forms uses to double buffer if the appropriate styles are set.  Let’s take a in-depth look at each in turn:

BufferedGraphicsManager

System.Drawing.BufferedGraphicsManager is a pretty simple class that has a static constructor that creates a BufferedGraphicsContext instance that you can obtain through the Current property.  It also registers itself with the ProcessExit and DomainUnload events so that it can call Invalidate() on the created BufferedGraphicsContext as the app shuts down.

BufferedGraphicsContext

The BufferedGraphicsContext class handles allocating and releasing the off-screen bitmap that you draw to (which you access through the BufferedGraphics class it gives back to you).  You can get an app-wide instance of this class from the BufferedGraphicsManager.Current property.  You can also manually create a BufferedGraphicsContext class, but then you are responsible for calling Dispose() when you are finished with it to release created resources.  Note that disposing of a context class will delete its BufferedGraphics object if it has one active. (An alternative to calling Dispose is calling Invalidate(), which will call Dispose() when you dispose of the BufferedGraphics object created through the context.)
 
Calling Allocate() from a BufferedGraphicsContext will create a compatible Graphics object / Display Device Context (DC) that wraps an off-screen bitmap for the specified Graphics object / DC.  This new Graphics object is accessed through the Graphics property of the BufferedGraphics object.  [Win32 Fyi:  The bitmap is created using CreateDIBSection.  hSection is set to NULL which allows the system to allocate the memory for the bitmap, which is then deallocated when the class later calls DeleteObject on the bitmap handle.]

Setting the MaximumBuffer property sets the threshold for the bitmap size that is kept in memory after disposing of a BufferedGraphics object.  If the size requested is larger than the MaximumBuffer size a new temporary BufferedGraphicsContext is created that will automatically be disposed of when the BufferedGraphics object it returns is disposed.  (Note that if the context has already created a BufferedGraphics object that has not been disposed a temporary BufferedGraphicsContext will also be created.)

BufferedGraphics

This is the object that provides you access to the off-screen bitmap that a BufferedGraphicsContext class creates (through Allocate()).  You can draw to the Graphics object returned from the Graphics property just like you normally would.  When you’re finished drawing, calling Render() on the BufferedGraphics object will call the Win32 BitBlt function using the SRCCOPY ROP method (just copies), positioning the bitmap based off of the Position property of the rectangle passed to the Allocate() method when creating this BufferedGraphics object.   By default this copies to the Graphics object / DC specified in the Allocate() method, but you can specify any Graphics object / DC as a destination through the Render() overloads.

The Windows Forms WM_PAINT Handler

I’ve included some (very) pseudo-code here to help give you an idea of what happens when a Windows Forms control handles the WM_PAINT message from Windows.  This is important to look at to understand how the different styles will affect the painting process.  (Note that there are also calls to BeginPaint/EndPaint and SelectPalette.  The HDC is also set up.)

if (OptimizedDoubleBuffer || (DoubleBuffer && UserPaint && AllPaintingInWmPaint))
{
create a BufferedGraphics from the context returned from the BufferedGraphicsManager
set the clipping rectangle
save the Graphics state
call OnPaintBackground (which calls PaintBackground()) if not Opaque style
restore the Graphics state
call OnPaint which then raises the Paint event
render the BufferedGraphics object
}
else
{
if (AllPaintingInWmPaint) call OnPaintBackground delegates if not Opaque style // unless an HDC is passed in WPARAM, not sure when this will happen
call OnPaint which then raises the Paint event
}

Two crucial things:

  1. Don't turn on double buffering if you're handling it yourself. If you have double buffering turned on and you're attempting handle your own double buffering of it you'll end up copying your back buffer into another back buffer that is then copied to the screen.
  2. The other interesting note here is that WM_ERASEBACKBROUND message handler checks for the the AllPaintingInWmPaint style.  You still get the background painted for you in the WM_PAINT handler if you don't have the Opaque style set.

Key Takeaways:

  • Don’t bother with the BufferedGraphics classes if you want simple double buffering.  Set the DoubleBuffered property to true.
  • If you want to handle your own double buffering:
    • Set the DoubleBuffered property and the DoubleBuffer style to false.
    • Override OnPaint() . Don’t call the base unless you deliberately want the Paint event raised.
  • If you are handling the background yourself set the AllPaintingInWmPaint and the Opaque styles.
  • If you want to minimize allocations of bitmaps:
    • Ensure you have created your own separate BufferedGraphicsContext classes for each BufferedGraphics you intend to use.
    • Ensure your MaximumBuffer property is large enough to hold the size of the off-screen bitmap.
    • Always call Dispose() on a BufferedGraphics class when you're done drawing.  (Specifically, before you call Allocate again on it's hosting BufferedGraphicsContext.)
  • Call Dispose() on any BufferedGraphicsContext class you create yourself when you're finished with them.
  • Remember the limitations:
    • You can’t change where the back buffer is copied to after allocating. (The offset in the target DC, that is.)
    • You can’t specify the raster operation (ROP).

 

That's about it for now.  This ended up being much more detailed than I expected and I've gotten a bit drowsy so hopefully this came through somewhat clear.  Any comments/corrections are welcome.