PDA

View Full Version : Eliminating Flicker



lnpneil
07-11-2002, 08:32 PM
Hi,

This is my first post and I'm pretty new to Delphi so be nice... :wink:

I'm in the process of writing a tile based game (along the lines of the original Ultima games) and I was encountering a slight problem with the graphics flickering when I re-draw the image (I'm using a TBitmap canvas).

I'm looking for the best way to eliminate this; I'm not lazy so I have been trying myself and so far here are my results...

1) Creating the image made up from the tiles on a seperate canvas then just copying that canvas to the visible canvas on screen when it is fully updated... doesn't eliminate flicker (I thought this was double-buffering? Am I wrong? Or just doing it incorrectly?)

2) Intercepting the windows paint messages a la the bitmap example in the help files (I'm using Delphi 6). This is fine except that *only* my image that is made up of the tiles is being updated now not the rest of the form. ie, this solution doesn't really work for me either.

3) Form1.Doublebufferd := True; This solution works perfectly on almost every level... In other words it works but it gives me no understanding of why or how (which is what I'd like!!)

Can anybody help?! I'm looking for suggestions on how to eliminate the flicker using these or other methods and also a more clear understanding of the DoubleBuffered property of forms?

Thanks!

Neil

Zanthos
07-11-2002, 08:51 PM
First off I had problems with the TBitmap canvas when I was doing bitmap tile manipulation stuff using the GDI, a quick fix,.. use the TImage component instead ;) For some reason it doesn't flicker (is it using double buffering behind its public interface? i don't know :))

Double buffering is, as you implemented, where instead of drawing to the bitmap you can see, you draw to an offscreen surface, then copy this to the on screen bitmap, this reduces flicker. The copying from the back buffer to the on screen surface is called Page Flipping.

As for point 1), you were probably implementing it correctly, but not copying to the display in synch with the vertical refresh(which is the main cause of flickery displays).

With DirectX the page flipping is done for you and VSynched to prevent flicker. But using GDI, you may be better off using the TImage component :)

lnpneil
07-11-2002, 08:58 PM
Hi Zanthos,

Thanks for your quick reply :)

I noticed I'm getting flicker even when using the TImage component aswell... maybe I'm just unlucky :wink:

A further question arises from your post though and that is how do I sync the redraw to the vertical refresh? Is this possible? Is there some method I can use to wait until just before/just after a refresh and then copy my image to the visible canvas?

Thanks again!

Neil

Thinking a sig is harder to think of than I thought.

Alimonster
07-11-2002, 09:09 PM
There's a bug in TImage! It *will flicker* if you don't set its Stretch property to true. Strange, but true, no less (pardon that semi-pun).

Now, back to your other queries from the original post.

The flicker is caused by Windows being a pain in the arse. Whenever it updates a region, it clears the background to the form's colour first *before* you draw stuff onto it. The net result of this is that your program shows a 'grey' colour (usually, whatever your form's background colour is tho) which immediately gets overridden by whatever you draw. This is bad.

There are several ways to combat it. The first is to take the problem at the source - Windows sends a message WM_ERASEBKGND whenever it has to repaint the window. You can handle this and tell Windows "get lost, you pest!"

Have a look at the win32.hlp file (Win32 Programmer's Reference help file), assuming it exists in your version of Delphi. In the section under WM_ERASEBKGND, it tells you:


Return Values

An application should return nonzero if it erases the background; otherwise, it should return zero.

Remarks

The DefWindowProc function erases the background by using the class background brush specified by the hbrBackground member of the WNDCLASS structure. If hbrBackground is NULL, the application should process the WM_ERASEBKGND message and erase the background.
An application should return nonzero in response to WM_ERASEBKGND if it processes the message and erases the background; this indicates that no further erasing is required. If the application returns zero, the window will remain marked for erasing. (Typically, this indicates that the fErase member of the PAINTSTRUCT structure will be TRUE.)

In short, we catch the message and set its to something other than zero. This stops Windows from redrawing the background. To do this, you can add in a handler for your message. Find the interface section of the unit and look at the form's declaration:


type
TForm1 = class(TForm)
// some event handlers here
private
{ Private declarations }
// stuff here
procedure StopFlicker(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
public
{ Public declarations }
end;

Notice the added function here, StopFlicker. This catches the Windows message WM_ERASEBKGND (hence the extra 'message' bit tagged onto the end of the procedure. The info for the message is passed in as a var message. You can use var msg: TMessage btw, but there's a specific type for each message (usually), so there's no harm in using that instead.

Now, go find the 'implementation' section of your form and bung this in:


implementation

procedure TForm1.StopFlicker(var Msg: TWMEraseBkgnd);
begin
Msg.Result := 1;
end;


The above says "don't handle this message" (you may need a call to 'inherited' in the function - I forget!)

That's one way to stop flickering. The next is to figure out why the WM_ERASEBKGND message is being sent in the first place!

You can use the Invalidate procedure to update what needs to be drawn (as well as the InvalidateRgn procedure). These exist in the Windows API but are handled by Delphi. What you may not know, though, is the native API ones actually have another parameter, bErase, which says "do you want to erase the background when redrawing?" This *would* stop the flickering if we could access it!

It turns out that components can use the csOpaque style in ControlStyle to say "I draw myself fully. Don't erase the background!" This comes in handy if you're creating your own graphical controls. You can add this line in the constructor:

ControlStyle := ControlStyle + [csOpaque];

That line will reduce flicker for your controls.

This still hasn't got around to DoubleBuffered yet ;). When you enable DoubleBuffered for your TWinControl (i.e. the form in this case), it *doesn't* draw the stuff to screen immediately on an OnPaint event. Instead, it creates a device context (a thing you draw to) in memory and does its stuff there. The final result is copied to the screen. This means that you won't see anything being drawn while-it-is-being-rendered, as it were. The standard concept of double-buffering.

However, the best piece of advice I can give you is this: give up on TCanvas, TBitmap and all that malarky. There's an amazingly cool graphics library called "Graphics32" that is pretty much *flicker free* and gives you *optimised double buffering, alpha blending, rotation, scaling...* the whole works. Take the trouble to convert your stuff over to it. You'll be amazed at how much functionality it offers you.

Really. Get it here (http://www.g32.org/graphics32/).

lnpneil
07-11-2002, 09:20 PM
Hi Alimonster,

Thanks for the long reply... funnily enough I've just realised that I learnt about setting the DoubleBuffered property to true from a web search that hit on your website!! Amazing coincidence!

I'll take a look at the Graphics32 library when I get home from work this evening... :wink:

With respect to handling the WM_ERASEBACKGROUND message - I had tried that but as I said it was only redrawing the Image area... maybe I'll play around a little more and see what regions I can invalidate myself :wink:

You mentioned adding the line;

ControlStyle := ControlStyle + [csOpaque];

to the constructor of a graphical control. Can I do this with a TImage for example? Where is the constructor? Or do I have to explicity define it myself? Or can't I define it?

My experience so far is mainly of Turbo Pascal from college and everything else from fiddling around myself... if that helps explain my many questions :)

Thanks again!

Neil

Alimonster
07-11-2002, 09:29 PM
The "controlstyle" thingy is only useful if you're writing your own components, you see. You can try directly adding a line somewhere (e.g. the form's OnCreate) such as this: "SomeObject.ControlStyle := SomeObject.ControlStyle + [csOpaque];"

In the case of the TImage though, that's not the problem. Try this:

1) start a new project
2) dump down a TImage of a reasonable size
3) put a button down
4) put this code in the button's OnClick event handler:

procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
for i := 0 to 1000 do
Image1.Canvas.LineTo(Random(Image1.Width), Random(Image1.Height));
end;

Run the program and hold down return for a while with the button selected - you'll see the flicker.

5) Now, set the image's Stretch property to True and repeat.

Notice anything different? ;).

Seriously, though, I can't recommend Graphics32 enough. I still remember the time when I was trying to help someone reduce the flicker in an email conversation. We got somewhere, but a little while later he cheerfully announced "I've found a great library called Graphics32". He attached a beautiful, alpha-blended, dozens of sprites thingy. With no flicker.

Try it try it try it :P.

TheLion
07-11-2002, 10:23 PM
I always used the form for my Canvas game programming and with a few simple tweaks, no flickering! :)

If you are interested in using the form, take a look at my tutorial at :
http://www.lionprod.myweb.nl
in the tutorial section!

Alimonster
07-11-2002, 10:30 PM
Nice tutorial, TheLion! A quick tip: you don't have to use the GetSystemMetrics to get the screen height and width. There's an easier way:

var
VideoModeX: Integer;
VideoModeY: Integer;
begin
VideoModeX := Screen.Width;
VideoModeY := Screen.Height;
end;

;)

TheLion
07-11-2002, 10:31 PM
I know, however for some reason I just prefer this method...
why I don't know, maybe because it's closer to the API... :roll:

Thanks for your kind words! :)

lnpneil
07-11-2002, 10:45 PM
Hi,

Thanks for all your suggestions and stuff... and that tutorial was cool thanks TheLion :)

I'm off now to play with it all and see what I end up with :)

Neil