Reduce Flicker with Gdi+ and C++

Reduce flicker with GDI+ and C++

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).

How to avoid excessive screen flickering with GDI

I think what you're seeing is flicker, not tearing. To minimize flicker, your WM_PAINT should write to the window DC exactly once. Typically, this one operation is a BitBlt:

HDC hdc = BeginPaint(m_hwnd, &m_PaintStruct);
... paint to bitmap ...
BitBlt(hdc, ...); // blt from bitmap to screen
EndPaint(m_hwnd, &m_PaintStruct);

If you paint to the window DC in multiple steps, then you open the window for flicker.

Your description of the problem doesn't match the code. In your description, you say that you're blitting from the compatible DC to the window hDC. But in your code, your BitBlt is followed by a m_pPath->DrawTo(m_hDC). If a refresh occurs during the DrawTo, then the screen will refresh with a partially-drawn view, resulting in flicker.

WPF: GDI drawing in other process flickers

After doing some research I've come to the conclusion that using a transparent window will suit my case best. Here's how I came to that conclusion:

I've used an application to read window events to check if there was any continuous event stream that could be the window invalidation, but there wasn't.

I did find out a way that should make it possible to actually read when the game invalidates the window, but it's kind of a hack: It's possible to replace the game's d3d9.dll file with your own, calling all the original functions, but catching the events. I don't have any details on how or even if it works since having to distribute a dll to end-users is not an option for me, not to mention that this is in fact hacking.

Flickering while redrawing in MFC

Try Double Buffering. “Double buffering” refers to the technique of writing into a memory DC and then BitBlt-ing the memory DC to the screen.

In connection with Windows, this technique can be used to handle WM_PAINT messages. Your OnDraw function calls BitBlt to copy the memory DC into the screen DC. The memory DC is associated with a member variable in the view class, and is written to during times when no other messages are being handled.

Here is a link for some code that can help.

C# graphics flickering

First don't use CreateGraphics() unless you absolutely have to. Bind an event handler to OnPaint and call Invalidate() when you want to refresh the surface.

If you don't want it to flicker you'll need to double buffer your drawing surface. The easiest way to do this is to set your form's DoubleBuffered property to True.

I would highly recommend if you plan on extending this to do your drawing to the PictureBox control. PictureBox is double-buffered by default and allows you to control your drawing region much more simply.

In code:

public partial class Form1 : Form
{
int xFirst, yFirst;
Bitmap bm = new Bitmap(1000, 1000);
Graphics bmG;
Pen pen = new Pen(Color.Black, 1);
bool draw = false;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
bmG = Graphics.FromImage(bm);
bmG.Clear(Color.White);
}

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
xFirst = e.X;
yFirst = e.Y;
draw = true;
}

private void Form1_MouseUp(object sender, MouseEventArgs e)
{
bmG.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
draw = false;
Invalidate();
}

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (draw)
{
Invalidate();
}
}

private void Form1_Paint(object sender, PaintEventArgs e)
{
if (draw) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
} else {
e.Graphics.DrawImage(bm, 0, 0);
}
}
}

Edit:

Another issue, you are creating a private Pen member. Pens (and Brushes, as well as many GDI+ objects) represent handles to unmanaged objects that need to be disposed otherwise your program will leak. Either wrap them in using statements (the preferred and exception-safe way) or explicitly dispose of them in the form's Dispose method.

Alternatively in System.Drawing you can access some pre-built Pens and Brushes that don't need to be (and shouldn't be) disposed. Use them like:

    private void Form1_Paint(object sender, PaintEventArgs e)
{
if (draw) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(Pens.Black, xFirst, yFirst, e.X, e.Y);
} else {
e.Graphics.DrawImage(bm, 0, 0);
}
}

Drawing without flickering

You can easily implement double buffering in Win32 as well. Assuming you are doing your painting directly on the Window using its device context, do this instead:

Create a "memory" device context and do all your drawing on that device context, then copy the invalidated portions of the window to the actual device context when appropriate, using the BitBlt() function

There's a pretty good (albeit high level) overview here.



Related Topics



Leave a reply



Submit