Using Windows Vista Built-In Double Buffering

The solution to fixing flickering issues when drawing graphics is to use double buffering. Double buffering basically means that an off-screen buffer is created. Everything is rendered to this off-screen buffer and, when drawing is completed, this off-screen buffer is copied to the screen. The end result is that you do not see any flickering and you do not see the drawing being created part by part. A little unknown fact is that Windows Vista has built-in support for double buffering, so managing off-screen buffers, copying data, and so forth are all managed by Windows for you. This article will explain how to use that functionality.

Example Application

This article comes with an example application to illustrate the Windows Vista double buffering technique. For demonstration purposes, I created the following rendering function:

void RenderWindow(HDC hdc, RECT& rcClient)
{
   // Fill the entire background with a color
   HBRUSH hBrushWhite = (HBRUSH)GetStockObject(WHITE_BRUSH);
   FillRect(hdc, &rcClient, hBrushWhite);

   // Render some lines of text
   RECT rc = rcClient;
   rc.left += 16;
   static const int NUM = 10;
   for (int i=0; i<NUM; ++i)
   {
      if (bUseBufferedPainting)
         DrawText(hdc, L"Buffered Painting", -1, &rc, DT_TOP);
      else
         DrawText(hdc, L"Unbuffered Painting", -1, &rc, DT_TOP);
      // Spacing between the lines depends on the height of the window
      rc.top += (rcClient.bottom/NUM);
      // Wait a little bit to exaggerate the effect of flickering
      Sleep(10);
   }
}

The sleep call is added to slow down the rendering a little bit. The effect of this is that the flickering with non double buffering will be even more noticeable.

The WM_PAINT handler of the normal, unbuffered rendering is as follows:

case WM_PAINT:
   {
      RECT rc;
      GetClientRect(hWnd, &rc);

      hdc = BeginPaint(hWnd, &ps);
      RenderWindow(hdc, rc);
      EndPaint(hWnd, &ps);
   }
   break;

Nothing special up to now.

Using Double Buffering

Double buffering support in Windows Vista is called Buffered Painting. The first thing you need to do is to include the uxtheme header file and link with the uxtheme library. This can be done as follows:

#include <uxtheme.h>
#pragma comment(lib, "uxtheme.lib")

Before you use any buffered painting functions, it is highly recommended to call BufferedPaintInit. BufferedPaintInit should be called once in every thread that wants to use buffered painting. It will set up some internal buffers that will be reused with every buffered painting operation. Every call to BufferedPaintInit should be matched with a call to BufferedPaintUnInit. When you do not call BufferedPaintInit, it might be that buffered painting will still work but you will have degraded performance because some internal data structures will be created and destroyed every time you use buffered painting. A good idea is to call BufferedPaintInit in your WM_CREATE handler and to call BufferedPaintUnInit in your WM_NCDESTROY handler, as can be seen below.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
                         LPARAM lParam)
{
   int wmId, wmEvent;
   PAINTSTRUCT ps;
   HDC hdc;

   switch (message)
   {
   case WM_CREATE:
      BufferedPaintInit();
      break;
   case WM_NCDESTROY:
      BufferedPaintUnInit();
      break;
   // ...
   // ...
   default:
      return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

Now, to actually use the buffered painting, you call BeginBufferedPaint which will give you a new device context. This is the off-screen buffer to which you render everything. Once you call EndBufferedPaint, this off-screen buffer can be copied to the screen for you. Below is some code to demonstrate this.

case WM_PAINT:
{
   RECT rc;
   GetClientRect(hWnd, &rc);

   // Normal BeginPaint call
   hdc = BeginPaint(hWnd, &ps);

   if (bUseBufferedPainting)
   {
      // Try to use buffered painting
      HDC hNewDC;
      HPAINTBUFFER hBufferedPaint =
         BeginBufferedPaint(hdc, &rc, BPBF_COMPATIBLEBITMAP,
                            NULL, &hNewDC);
      if (hBufferedPaint)
      {
         // Starting of buffered painting was successful,
         // so render to the new device context
         RenderWindow(hNewDC, rc);
         // Stop buffered painting.
         // The TRUE means the off-screen buffer will be copied
         // to the hdc that was specified in the call
         // to BeginBufferedPaint.
         EndBufferedPaint(hBufferedPaint, TRUE);
      }
      else
      {
         // Buffered painting failed. Use unbuffered method,
         // rendering directly to the screen.
         RenderWindow(hdc, rc);
      }
   }
   else
   {
      RenderWindow(hdc, rc);
   }

   // Normal EndPaint call
   EndPaint(hWnd, &ps);
}
break;

With the call to BeginBufferedPaint, you start your buffered painting. The third parameter to this function defines the type of off-screen buffer that will be created. The following options are available (from MSDN):

  • BPBF_COMPATIBLEBITMAP: Compatible bitmap. The number of bits per pixel is based on the color format of the device associated with the HDC specified with BeginBufferedPaint—typically, this is the display device.
  • BPBF_DIB: Bottom-up device-independent bitmap. The origin of the bitmap is the lower-left corner. Uses 32 bits per pixel.
  • BPBF_TOPDOWNDIB:Top-down device-independent bitmap. The origin of the bitmap is the upper-left corner. Uses 32 bits per pixel.
  • BPBF_TOPDOWNMONODIB: Top-down, monochrome, device-independent bitmap. Uses 1 bit per pixel.

Setting this to, for example, BPBF_DIB will ensure you always have a 32 bit off-screen buffer, so you can always draw in 32 bit regardless of your current monitor settings.

Using Windows Vista Built-In Double Buffering

The fourth parameter to BeginBufferedPaint is a pointer to a BP_PAINTPARAMS structure. This can be NULL if you don't need it. Because the buffered painting technique will keep off-screen buffers alive and will reuse them in future buffered painting calls, it is important to remember that the contents of the off-screen buffer is not erased by default. So, either you fill the off-screen buffer yourself with a certain color to erase the previous drawing (as I did in my example above), or you can use the BP_PAINTPARAMS structure to force BeginBufferedPaint to clear the off-screen buffer with ARGB = {0,0,0,0}. A third way is to use the function BufferedPaintClear; this will clear a specific rectangle on the off-screen buffer to ARGB={0,0,0,0}. This function is faster than using GDI because it will access the bits directly, but you cannot specify a color. You also can change clipping behaviour on the off-screen buffer and specify blending parameters. The blending parameters are used to control blending operations when EndBufferedPaint copies the contents of the off-screen buffer to the destination.

When you would run the code above, you'll notice there is still some flickering. This is because Windows is still erasing the background before painting. Because you are rendering the entire window off-screen and EndBufferedPaint will copy the off-screen buffer in one go, you can bypass the erasing of the background. This can be done by handling WM_ERASEBKGND as follows:

case WM_ERASEBKGND:
   return 1;

There are two more functions that you might find useful with buffered painting. BufferedPaintSetAlpha sets all alpha values in the buffer to a certain value, 0-255, where 0 is fully transparent and 255 is fully opaque. This alpha value is used to control the amount of transparency when the off-screen buffer is copied to the destination. To set all alpha values to 255, you also can use the BufferedPaintMakeOpaque macro.

You can use GetBufferedPaintBits to get direct access to the bits of the off-screen buffer. This only works when the off-screen buffer is a device independent buffer (DIB).

The example that comes with this article shows the effect of using double buffering. Use the "Mode" menu to switch between buffered and unbuffered painting. To see the effect, resize the window and see how the window gets updated. Because buffered painting is available only since Windows Vista, the example application will not run on Windows XP or lower.

Windows Vista also supports buffered animations, but that's the topic for my next article.



About the Author

Marc Gregoire

Marc graduated from the Catholic University Leuven, Belgium, with a degree in "Burgerlijk ingenieur in de computer wetenschappen" (equivalent to Master of Science in Engineering in Computer Science) in 2003. In 2004 he got the cum laude degree of Master In Artificial Intelligence at the same university. In 2005 he started working for a big software consultancy company. His main expertise is C/C++ and specifically Microsoft VC++ and the MFC framework. Next to C/C++, he also likes C# and uses PHP for creating webpages. Besides his main interest for Windows development, he also has experience in developing C++ programs running 24x7 on Linux platforms and in developing critical 2G,3G software running on Solaris for big telecom operators.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds