21

I'm using GDI+ in a C++/MFC application and I just can't seem to avoid flickering whenever the window is resized.

I have already tried these steps:

  • returned TRUE on OnEraseBkGnd();
  • returned NULL on OnCtlColor();
  • used double buffering according to this code:

void vwView::OnDraw(CDC* pDC) 
{
   CRect rcClient;
   GetClientRect(rcClient);

   Bitmap bmp(rcClient.Width(), rcClient.Height());
   Graphics graphics(&bmp);

   graphics.DrawImage(m_image, rcClient.left, rcClient.top);

   Graphics grph(pDC->m_hDC);
   grph.DrawImage(&bmp, 0, 0);
}

Am I doing something wrong? Or is there another way to achieve this?

Shog9
  • 156,901
  • 35
  • 231
  • 235
djeidot
  • 4,542
  • 4
  • 42
  • 45

6 Answers6

43

To completely avoid flicker, you would need to complete all drawing in the interval between screen updates. Windows does not provide any easy means of accomplishing this for normal window painting (Vista provides composite drawing via the DWM, but this cannot be relied on even on systems running Vista). Therefore, the best you can do to minimize flicker is to draw everything as quickly as possible (reduce tearing by increasing your chances of completing all drawing within a refresh cycle), and avoid overdraw (drawing part of the screen and then drawing something else over the top: risks presenting user with a partially-drawn screen).

Let's discuss the techniques presented here so far:

  • Do-nothing OnEraseBkgnd(): helps to avoid over-draw by preventing the invalidated area of the window from being filled with the window's background color. Useful when you will be drawing the entire area again during WM_PAINT handling anyway, as in the case of double-buffered drawing... but see Notes on avoiding overdraw by preventing drawing after your WM_PAINT method.

  • Returning NULL for OnCtlColor(): this shouldn't actually do anything... unless you have child controls on your form. In that case, see Notes on avoiding overdraw by preventing drawing after your WM_PAINT method instead.

  • Double buffered drawing: helps to avoid tearing (and potentially overdraw as well), by reducing the actual on-screen drawing to a single BitBLT. May hurt the time needed for drawing though: hardware acceleration cannot be used (although with GDI+, the chances of any hardware-assisted drawing being used are quite slim), an off-screen bitmap must be created and filled for each redraw, and the entire window must be repainted for each redraw. See Notes on efficient double-buffering.

  • Using GDI calls rather than GDI+ for the BitBlt: This is often a good idea - Graphics::DrawImage() can be very slow. I've even found the normal GDI BitBlt() call to be faster on some systems. Play around with this, but only after trying a few other suggestions first.

  • Avoiding window class styles that force a full redraw on each resize (CS_VREDRAW, CS_HREDRAW): This will help, but only if you don't need to redraw the entire window when size changes.

Notes on avoiding overdraw by preventing drawing prior to your WM_PAINT method

When all or a portion of a window is invalidated, it will be erased and repainted. As already noted, you can skip erasing if you plan to repaint the entire invalid area. However, if you are working with a child window, then you must ensure that parent window(s) are not also erasing your area of the screen. The WS_CLIPCHILDREN style should be set on all parent windows - this will prevent the areas occupied by child windows (including your view) from being drawn on.

Notes on avoiding overdraw by preventing drawing after your WM_PAINT method

If you have any child controls hosted on your form, you will want to use the WS_CLIPCHILDREN style to avoid drawing over them (and subsequently being over drawn by them. Be aware, this will impact the speed of the BitBlt routine somewhat.

Notes on efficient double-buffering

Right now, you're creating a new back-buffer image each time the view draws itself. For larger windows, this can represent a significant amount of memory being allocated and released, and will result in significant performance problems. I recommend keeping a dynamically-allocated bitmap in your view object, re-allocating it as needed to match the size of your view.

Note that while the window is being resized, this will result in just as many allocations as the present system, since each new size will require a new back buffer bitmap to be allocated to match it - you can ease the pain somewhat by rounding dimensions up to the next largest multiple of 4, 8, 16, etc., allowing you to avoid re-allocated on each tiny change in size.

Note that, if the size of the window hasn't changed since the last time you rendered into the back buffer, you don't need to re-render it when the window is invalidated - just Blt out the already-rendered image onto the screen.

Also, allocate a bitmap that matches the bit depth of the screen. The constructor for Bitmap you're currently using will default to 32bpp, ARGB-layout; if this doesn't match the screen, then it will have to be converted. Consider using the GDI method CreateCompatibleBitmap() to get a matching bitmap.

Finally... I assume your example code is just that, an illustrative snippet. But, if you are actually doing nothing beyond rendering an existing image onto the screen, then you don't really need to maintain a back buffer at all - just Blt directly from the image (and convert the format of the image ahead of time to match the screen).

Community
  • 1
  • 1
Shog9
  • 156,901
  • 35
  • 231
  • 235
  • 2
    So "reducing the actual on-screen drawing to a single BitBLt" isn't actually enough to prevent all tearing? Like windows might allow that single bitblt to occur in the middle of a screen refresh, allowing for tearing even though it's a single operation? – rogerdpack May 09 '12 at 14:28
  • 2
    Sure. It takes time to transfer the actual data, how long depending a lot on the setup of a particular machine. – Shog9 May 09 '12 at 14:31
4

You might try using old-fashioned GDI rather than GDI+ to write to the DC, especially since you're already buffering the image. Use Bitmap::LockBits to access the raw bitmap data, create a BITMAPINFO structure, and use SetDIBitsToDevice to display the bitmap.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
3

You may get some traction through using Direct3D to "tell" you when vsync's occur, et al, so you can BitBlt/update at a good time. See GDI vsync to avoid tearing (though getting things down to a single small BitBlt may be "good enough" for some cases).

Also note that it appears that GDI BitBlt isn't synchronized with screen vsync. See Faster than BitBlt.

Also note that using CAPTUREBLT (which allows you to capture transparent windows) causes the mouse to flicker (if aero is not in use) if used.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
  • 1
    That last "Faster than BitBlt" forum post [seems like it hasn't been available since April of 2015](https://web.archive.org/web/20150423163628/http://www.itlisting.org/4-windows-ce-embedded/7752b5a1dabede5f.aspx). – jrh Jun 11 '18 at 15:10
3

Make sure that the window class for the window does not include the CS_VREDRAW and CS_HREDRAW flags in its style.

See http://msdn.microsoft.com/en-us/library/ms633574(VS.85).aspx

Frederik Slijkerman
  • 6,471
  • 28
  • 39
  • It looks like the window style descriptions have been moved to [Window Class Styles](https://msdn.microsoft.com/en-us/library/ff729176(v=vs.85).aspx). – jrh Jun 11 '18 at 15:15
2

This link contains some useful info: http://www.catch22.net/tuts/flicker-free-drawing

(I know this is a very late addition to the thread, but this is for anyone (like me) who found it when looking to reduce flicker in my Win32 app...)

Liam
  • 1,472
  • 1
  • 10
  • 14
  • This is more or less a link only answer [which is discouraged](https://meta.stackexchange.com/a/8259/321006). As of 6/11/2018 this looks like pretty much a *dead link only answer*, though readers should note that the site hasn't been "under maintenance" [all that long (it was valid in 2017)](http://web.archive.org/web/20170102192034/http://www.catch22.net:80/tuts/flicker-free-drawing). Personally I'm alright with this I guess, the page has useful advice. Hopefully it will come back soon. I recommend including content from the page (in your own words) in your answer, in any case. – jrh Jun 11 '18 at 15:12
0

Are there child windows on the form? The Window manager starts by getting the parent window to erase its background by sending a WM_ERASEBKGND message, THEN it sends a wM_PAINT message - presumably this maps to your wx::OnDraw method. Then it iterates over each child control and gets those to paint themselves.

If this is your scenario... using Vistas new aero look would solve your problem as the aero desktop window manager does window compositing automatically. With the older window manager its a pita.

Chris Becke
  • 34,244
  • 12
  • 79
  • 148