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.