GDI+ Double Buffering  3rd August 2007

GDI+ has been about for a while now and, from a developer's perspective, is an improvement over the old GDI API. It not only simplifies application development, via it's class based organisation, but allows some quite startling graphical and textual effects to be used.

Although GDI+ is not hardware accelerated, the performance of this API is acceptable, if you bear in mind that it was never intended to be used for game or animation development. Applications which do need to render complex scenes or move a large number of objects about would have to use OpenGL or DirectX, which are both hardware assisted.

If you are using GDI+, there are a couple of ways you can (must) boost the apparent performance of your application - they are respectively double buffering and use of a cached bitmap. Details of these two techniques are presented below.

How does double buffering work then? Well, to put it simply, an off-screen buffer is created of the same dimensions as the output screen or window. All drawing operations are targeted at this buffer using standard GDI+ functions. Once these drawing operations have completed, the buffer contents are drawn, or 'flipped', very quickly to the output screen.

To use double buffering, an in-memory bitmap needs to be created specifying the size of the screen or window to which the graphics will ultimately be rendered. An example can be seen below.

Bitmap* pMemoryBMap = new Bitmap(lWidth, lHeight);

The next step requires the creation of a graphics canvas associated with the new bitmap above. This canvas will be used draw our individual graphics objects to the buffer.

Graphics* pCanvas = Graphics::FromImage(pMemoryBMap);

Remember to paint the buffer background a particular colour at this point - white in the example below.

Color backgroundColour(0xFF, 0xFF, 0xFF, 0xFF);
pCanvas->Clear(backgroundColour);

All graphics objects can now be drawn to the buffer as if they were being drawn on screen directly. When all drawing has been completed, a new graphics canvas should be created, but this time it should be associated with the actual output screen or window, as detailed below.

Graphics* pGraphics = new Graphics(hWnd);

Now, the buffered graphics can be rendered to the screen using the DrawImage method of the graphics canvas, as detailed below.

pGraphics->DrawImage(pMemoryBMap, 0, 0);

The contents of the buffer should be displayed instantly without any lag. Aesthetically, it is more pleasing to see a scene rendered completely than to be able to see the individual graphics objects being drawn one at a time.

Now, how does use of a cached bitmap help the performance of an application? Well, the main reason for using a cached bitmap is to aid in the re-drawing of the current screen or window when a WM_PAINT message is received. Typically, an application will have moved on by drawing it's next off-screen buffer and will use a cached bitmap to refresh the current screen contents, if required, before the next buffer is flipped to the screen.

A cached bitmap should be generated, as detailed below, from the off-screen buffer after the contents of the buffer have been flipped to the screen.

CachedBitmap* m_pCachedBitmap = new CachedBitmap(pMemoryBMap, pGraphics);

Note, in addition to a pointer for the off-screen buffer, a canvas pointer associated with the actual output screen must be supplied when the cached bitmap is created. Should the current screen output need to be re-drawn, perhaps in response to a WM_PAINT message as described above, the cached bitmap can be displayed as follows.

pGraphics->DrawCachedBitmap(m_pCachedBitmap, 0, 0);

Use of a cached bitmap, as described above, should ensure that your screen or window is re-painted smoothly without any flickering. When the contents of your display need to be completely refreshed, double buffering will allow the screen to be rendered in its entirety, in one pass.