How can I get rid of jerkiness in scrolling WinForms animations?

I am writing a simple control in C # that works like a box with pictures, except that the image constantly scrolls up (and appears again from below). The animation effect is controlled by a timer (System.Threading.Timer), which copies from the cached image (in two parts) to a hidden buffer, which is then displayed on the control surface in its Paint event.

The problem is that this scroll animation effect is slightly jerky when working with a high frame rate of 20 frames per second (at lower frame rates, the effect is too small to be perceived). I suspect that this rage is caused by the fact that the animation is in no way synchronized with the refresh rate of the monitor, which means that each frame remains on the screen for a variable duration, and not exactly 25 milliseconds.

Is there any way to make this animation scroll smoothly?

You can download the sample application here (run it and click "start"), and the source code is here . It does not look terribly jerky, but if you look at it carefully, you will see hiccups.

WARNING : this animation creates a rather strange optical illusion effect that can make you a little sick. If you watch it for a while and then turn it off, it will look as if your screen is stretched vertically.

UPDATE : as an experiment, I tried to create an AVI file with my scroll bitmaps. The result was less dramatic than my WinForms animation, but still unacceptable (and it still hurt me to watch it for too long). I think that I was faced with the fundamental problem of not synchronizing with the refresh rate, so I may have to keep people sick of my appearance and personality.

+4
source share
9 answers

Before you draw a buffered image, you need to wait for VSYNC .

There is a CodeProject article that suggests using a multimedia timer and the DirectX IDirectDraw::GetScanLine() method.

I'm sure you can use this method with Managed DirectX from C #.

EDIT:

After a few more studies and searches, I came to the conclusion that drawing through GDI is not real-time, and even if you draw the exact time, it may happen too late and you will have a break.

So, with GDI, this seems impossible.

+3
source

( http://www.vcskicks.com/animated-windows-form.html )

This link has animation, and they explain how they perform it. There is also a sample project that you can download to see it in action.

+3
source

Use double buffering. Here are two articles: 1 2 .

Another factor worth considering is that using a timer does not guarantee that you will be called at the right time. The right way to do this is to look at the time elapsed since the last draw and calculate the correct distance for a smooth transition.

+2
source

I had a similar problem a couple of months ago, and I decided to switch them to WPF. The animated control worked much smoother than the standard timer-based solution, and I no longer had to worry about synchronization.

You might want to try it.

+2
source

You need to stop relying on the timer to trigger when you ask for it, and instead work out the time difference, and then work out the distance to move. This is what games and WPF do, so they can achieve smooth scrolling.

Let's say you know that you need to move 100 pixels in 1 second (to synchronize with music), then you calculate the time since the last timer event was fired (suppose it was 20 ms) and work out the distance to the move as a fraction of the total ( 20 ms / 1000 ms * 100 pixels = 2 pixels).

A rough code example (not verified):

 Image image = Image.LoadFromFile(...); DateTime lastEvent = DateTime.Now; float x = 0, y = 0; float dy = -100f; // distance to move per second void Update(TimeSpan elapsed) { y += (elapsed.TotalMilliseconds * dy / 1000f); if (y <= -image.Height) y += image.Height; } void OnTimer(object sender, EventArgs e) { TimeSpan elapsed = DateTime.Now.Subtract(lastEvent); lastEvent = DateTime.Now; Update(elapsed); this.Refresh(); } void OnPaint(object sender, PaintEventArgs e) { e.Graphics.DrawImage(image, x, y); e.Graphics.DrawImage(image, x, y + image.Height); } 
+2
source

Some ideas (not everything is good!):

  • When using a streaming timer, make sure that the rendering time is much shorter than the interval between frames (due to the sound of your program you should be fine). If rendering takes more than 1 frame, you will receive repeated calls and start rendering a new frame before you finish the last. One solution to this is to register only one callback at startup. Then, in your callback, set up a new callback (instead of just asking to be called again every n milliseconds). This way you can ensure that you only plan on a new frame when you finish rendering the current one.

  • Instead of using a thread timer that will call you back at an undetermined period of time (the only guarantee is that it is greater than or equal to the interval you specify), start the animation in a separate thread and just wait (waiting for a waiting cycle or spinway) until the time comes next frame. You can use Thread.Sleep to sleep for shorter periods to avoid using 100% CPU or Thread.Sleep (0), just to get and get another timelapse as soon as possible. This will help you get much more consistent frame intervals.

  • As mentioned above, use the time between frames to calculate the distance to scroll, so the scroll speed is independent of frame rate. But note that you will get temporary sampling / smoothing effects if you try to scroll at non-pixel speed (for example, if you need to scroll 1.4 pixels per frame, you can best do 1 pixel, which will give 40% speed). A workaround for this would be to use a larger specular bitmap for scrolling, and then scale it while blending on the screen so that you can efficiently scroll through the subpixel sums.

  • Use higher thread priority. (really nasty, but it can help!)

  • Use something more manageable (DirectX) rather than GDI for rendering. This can be configured to replace the external screen with vsync. (I'm not sure if Forms double buffering worries synchronization)

+1
source

I had the same problem and found that this is a problem with the video card. Are you sure your video card can handle this?

+1
source

I modified your example to use a multimedia timer with an accuracy of 1 ms, and most of the uncertainty disappeared. However, there are still a few tears left, depending on where exactly you are dragging the window vertically. If you want a complete and perfect solution, GDI / GDI + is probably not your way because (AFAIK) it does not give you control over vertical synchronization.

+1
source

Well, if you want to start the timer at a lower speed, you can always change the number of images that scrolls in the view. This gives a better preliminary readiness, but makes the effect look disgusting.

Just change _T + = 1; to add a new step ...

In fact, you can add a property to the control to customize the step.

0
source

Source: https://habr.com/ru/post/1285742/


All Articles